<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/%E6%AD%A6%E6%B1%8932x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/%E6%AD%A6%E6%B1%8916x16.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib1/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib1/pace/pace-theme-center-simple.min.css">
  <script src="/lib1/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"example.com","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta property="og:type" content="website">
<meta property="og:title" content="Technological Blog">
<meta property="og:url" content="http://example.com/page/10/index.html">
<meta property="og:site_name" content="Technological Blog">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="Li Yudong">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="http://example.com/page/10/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : true,
    isPost : false,
    lang   : 'zh-CN'
  };
</script>

  <title>Technological Blog</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">Technological Blog</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">IT小白的成长之旅</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="tags fa-fw"></i>标签<span class="badge">18</span></a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="th fa-fw"></i>分类<span class="badge">14</span></a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="archive fa-fw"></i>归档<span class="badge">95</span></a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

  <a href="https://github.com/tiarmor1" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content index posts-expand">
            
      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2020/08/21/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2020/08/21/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/" class="post-title-link" itemprop="url">循环神经网络</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2020-08-21 22:33:17" itemprop="dateCreated datePublished" datetime="2020-08-21T22:33:17+08:00">2020-08-21</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:23:42" itemprop="dateModified" datetime="2021-11-03T16:23:42+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/" itemprop="url" rel="index"><span itemprop="name">机器学习</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h2 id="1、语言模型"><a href="#1、语言模型" class="headerlink" title="1、语言模型"></a>1、语言模型</h2><p>与多层感知机和能有效处理空间信息的卷积神经网络不同，循环神经网络是为了更好地处理时序信息而设计的。引入状态变量来存储过去的信息，并用其与当前的输入共同决定当前的输出。</p>
<p>语言模型：可将自然语言文本看作一段离散的时间序列，假设一段长度为T的文本中的词依次为W1、W2….Wt，那么在离散的时间序列中，Wt可看作在时间步t的输出或标签。给定一个长度为T的词的序列，语言模型将计算该序列概率：P(W1,W2,….,Wt)。为计算该语言模型，需要先计算词的概率，以及一个词在给定前几个词的情况下的条件概率。</p>
<p>N元语法：计算和存储多个词的概率复杂度会呈指数级增加，故通过马尔可夫假设简化语言模型：一个词的出现只与前面N个词相关，即N阶马尔可夫链。</p>
<p>故基于n-1阶马尔可夫链，可将语言模型改写为：<br>$$<br>P(W1,W2,…,Wt) = ∏P（Wt|Wt-(n-1),…,Wt-1）<br>$$<br>以上也叫做n元语法，当n较小时，n元语法往往不准确，当n较大时，n元语法需要计算并存储大量的词频和多词相邻频率。</p>
<h2 id="2、循环神经网络"><a href="#2、循环神经网络" class="headerlink" title="2、循环神经网络"></a>2、循环神经网络</h2><p>循环神经网络：并非刚性地记忆所有固定长度的序列，而是通过隐藏状态来存储之前时间步的信息。利用之前的多层感知机，通过添加隐藏状态将其变成循环神经网络。</p>
<h3 id="不含隐藏状态的多层感知机："><a href="#不含隐藏状态的多层感知机：" class="headerlink" title="不含隐藏状态的多层感知机："></a>不含隐藏状态的多层感知机：</h3><p>样本数为n、输出个数为d的小批量数据样本X。设隐藏层的激活函数为f，则其输出H为：<br>$$<br>H = f（X Wxh + bh）<br>$$<br>其中隐藏层权重参数W为Rn*d，隐藏层偏差参数b为R1xh，h为隐藏单元个数。上式两者根据广播机制来相加，设输出层的输出个数为q，则输出层的输出为：<br>$$<br>O = HWhq +bq<br>$$<br>若为分类问题，可用softmax(O)来计算输出类别的概率分布。</p>
<h3 id="含隐藏状态的多层感知机"><a href="#含隐藏状态的多层感知机" class="headerlink" title="含隐藏状态的多层感知机"></a>含隐藏状态的多层感知机</h3><p>与上一个相区别的是，保存上一时间步的隐藏变量Ht-1，并引入一个新的权重参数Whh为Rhxh，该参数用于描述在当前时间步如何使用上一时间步的隐藏变量。<br>$$<br>Ht = f(Xt Wxh + Ht-1Whh +bh)<br>$$<br>与多层感知机相比，添加了隐藏变量来捕捉截至当前时间步的序列的历史信息，就像神经网络当前时间步的状态或记忆一样，因此也称为隐藏状态，由于在当前时间步使用了上一时间步的隐藏状态，因此计算是循环的。通过对上一次时间步的利用，其模型参数的数量不随时间步的增加而增长。将输入与前一时间步隐藏状态连结后，输入至一个激活函数为f的全连接层，该全连接层的输出就是当前时间步的隐藏状态，且模型参数为Wxh与Whh的连结，偏差为bh。</p>
<p><img src="/../../image/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/%E9%9A%90%E8%97%8F%E7%8A%B6%E6%80%81%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C.png" alt="隐藏状态循环神经网络"></p>
<h2 id="3、基于字符级神经网络的语言模型"><a href="#3、基于字符级神经网络的语言模型" class="headerlink" title="3、基于字符级神经网络的语言模型"></a>3、基于字符级神经网络的语言模型</h2><p><img src="/../../image/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/%E5%AD%97%E7%AC%A6%E7%BA%A7%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C.png" alt="字符级循环神经网络"></p>
<p>演示如何1基于当前与过去字符来预测下一个字符，训练时对每个时间步的输出层输出使用softmax运算，然后使用交叉熵损失函数来计算其与标签的误差。</p>
<h3 id="处理数据集"><a href="#处理数据集" class="headerlink" title="处理数据集"></a>处理数据集</h3><p>建立字符索引：将每个字符映射成一个从0开始的连续整数，又称索引以方便后续处理。为得索引，我们将数据集中所有不同字符取出来，并逐一映射到索引来构造字典。</p>
<p>时序数据采样：每次随机读取小批量样本和标签，样本包含连续的字符。例：时间步数为5，样本序列为5个字符：‘’想，要，有，直，升”，则其标签序列为这些字符在训练集中的下一个字符：“要，有，直，升，机”。</p>
<p>随机采样：每个样本为原始序列上任意截取的一段序列，相邻的两个随机小批量不一定相邻，故每次随机采样前都需要重新初始化隐藏状态。</p>
<p>相邻采样：令相邻的两个随机小批量在原始序列上的位置也相毗邻，此时可以用一个小批量最终时间步的隐藏状态来初始化下一个小批量的隐藏状态：如此循环造成的影响：1、训练模型时，只需在每个迭代周期开始时初始化隐藏状态；2、当多个小批量通过传递隐藏状态串联起来时，梯度计算将依赖串联起的序列，迭代次数增加，梯度开销会越来越大。</p>
<h3 id="one-hot向量"><a href="#one-hot向量" class="headerlink" title="one-hot向量"></a>one-hot向量</h3><p>使用one-hot向量将词表示成向量输入到神经网络：每个字符已经同一个从0到N-1的连续整数值索引一一对应。如果一个字符的索引是i，则其向量为全0的长为N的向量，仅将位置为i的元素设为1。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">to_one_hot</span><span class="params">(X,size)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> [nd.one_hot(x,size) <span class="keyword">for</span> x <span class="keyword">in</span> X.T]</span><br></pre></td></tr></table></figure>



<h3 id="构建模型"><a href="#构建模型" class="headerlink" title="构建模型"></a>构建模型</h3><p>先初始化模型参数，将隐藏单元个数num_hiddens作为超参数，</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">num_inputs, num_hiddens, num_outputs = vocab_size, <span class="number">256</span>, vocab_size</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_params</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_one</span><span class="params">(shape)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> nd.random.normal(scale=o.o1, shape=shape, ctx=ctx)</span><br><span class="line">    <span class="comment">#先定义隐藏层参数</span></span><br><span class="line">    W_xh = _one((num_inputs, num_hiddens))</span><br><span class="line">    W_hh = _one((num_hiddens, num_hiddens))</span><br><span class="line">    b_h = nd.zeros(num_hiddens, ctx=ctx)</span><br><span class="line">    <span class="comment">#输出层参数</span></span><br><span class="line">    W_hq = _one((num_hiddens, num_outputs))</span><br><span class="line">    b_q = nd.zeros(num_outputs, ctx=ctx)</span><br><span class="line">    <span class="comment">#附上梯度</span></span><br><span class="line">    params = [W_xh, W_hh, b_h, W_hq, b_q]</span><br><span class="line">    <span class="keyword">for</span> param <span class="keyword">in</span> params:</span><br><span class="line">        param.attach_grad()</span><br><span class="line">    <span class="keyword">return</span> params</span><br></pre></td></tr></table></figure>

<p>定义init_rnn_state函数来返回初始化的隐藏状态，返回由一个形状为（批量大小，隐藏单元个数）的值为0的NDArray组成的元组。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">init_rnn_state</span><span class="params">(batch_size, num_hiddens, ctx)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> (nd.zeros(shape=(batch_size, num_hiddens), ctx=ctx))</span><br></pre></td></tr></table></figure>

<p>定义rnn函数来在一个时间步中计算隐藏状态与输出，激活函数使用tanh</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">rnn</span><span class="params">(inputs, state, params)</span>:</span></span><br><span class="line">    <span class="comment">#inputs和outputs皆为num_steps个形状为（batch_size, vocab_size)的矩阵</span></span><br><span class="line">    W_xh, W_hh, b_h, W_hq, b_q = params</span><br><span class="line">    H, = state</span><br><span class="line">    outputs = []</span><br><span class="line">    <span class="keyword">for</span> X <span class="keyword">in</span> inputs:</span><br><span class="line">        H = nd.tanh(nd.dot(X, W_xh) + nd.dot(H, W_hh) + b_h)</span><br><span class="line">        Y = nd.dot(H, W_hq) + b_q</span><br><span class="line">        outputs.append(Y)</span><br><span class="line">    <span class="keyword">return</span> outputs, (H,)</span><br></pre></td></tr></table></figure>



<h3 id="定义预测函数"><a href="#定义预测函数" class="headerlink" title="定义预测函数"></a>定义预测函数</h3><p>基于前缀prefix（含有数个字符的字符串）来预测接下来的num_chars个字符:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">predict_rnn</span><span class="params">(prefix, num_chars, rnn, params, init_rnn_state, </span></span></span><br><span class="line"><span class="function"><span class="params">                num_hiddens, vocab_size, ctx, idx_to_char, char_to_idx)</span>:</span></span><br><span class="line">    state = init_rnn_state(<span class="number">1</span>, num_hiddens, ctx)</span><br><span class="line">    output = [char_to_idx[prefix[<span class="number">0</span>]]]</span><br><span class="line">    <span class="keyword">for</span> t <span class="keyword">in</span> range(num_chars + len(prefix) - <span class="number">1</span>):</span><br><span class="line">        <span class="comment">#将上一时间步的输出作为当前时间步的输入</span></span><br><span class="line">        X = to_onehot(nd.array([output[<span class="number">-1</span>]], ctx=ctx), vocab_size)</span><br><span class="line">        <span class="comment">#计算输出和更新隐藏状态</span></span><br><span class="line">        (Y, state) = rnn(X, state, params)</span><br><span class="line">        <span class="comment">#下一个时间步的输入是prefix里的字符或者当前的最佳预测字符</span></span><br><span class="line">        <span class="keyword">if</span> t &lt; len(prefix) - <span class="number">1</span>:</span><br><span class="line">            output.append(char_to_idx[prefix[t + <span class="number">1</span>]])</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            output.append(int(Y[<span class="number">0</span>].argmax(axis=<span class="number">1</span>).asscalar()))</span><br><span class="line">        <span class="keyword">return</span> <span class="string">''</span>.join([idx_to_char[i] <span class="keyword">for</span> i <span class="keyword">in</span> output])</span><br></pre></td></tr></table></figure>



<h3 id="裁剪梯度"><a href="#裁剪梯度" class="headerlink" title="裁剪梯度"></a>裁剪梯度</h3><p>利用裁剪梯度以应对梯度爆炸，假设将所有模型参数梯度的元素拼接成一个向量g，并设裁剪的阈值为s;裁剪后的梯度的L2范数不超过s<br>$$<br>裁剪后的梯度为 min(s/||g|| , 1)g<br>$$</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">grad_clipping</span><span class="params">(params, theta, ctx)</span>:</span></span><br><span class="line">    norm = nd.array([<span class="number">0</span>], ctx)</span><br><span class="line">    <span class="keyword">for</span> param <span class="keyword">in</span> params:</span><br><span class="line">        norm += (param.grad **<span class="number">2</span>).sum()</span><br><span class="line">    norm = norm.sqrt().asscalar()</span><br><span class="line">    <span class="keyword">if</span> norm &gt; theta:</span><br><span class="line">        <span class="keyword">for</span> param <span class="keyword">in</span> params:</span><br><span class="line">            param.grad[:] *= theta / norm</span><br></pre></td></tr></table></figure>



<h3 id="模型训练函数"><a href="#模型训练函数" class="headerlink" title="模型训练函数"></a>模型训练函数</h3><p>困惑度用于评价语言模型的好坏，是对交叉熵损失函数做指数运算后得到的值。</p>
<p>与之前章节的训练函数相比，该模型训练有几点不同：1、用困惑度评价模型；2、在迭代模型参数前裁剪梯度；3、对时序数据采用不同的采样方法将导致隐藏状态初始化的不同。</p>
<h3 id="使用Gluon进行模型的简洁实现"><a href="#使用Gluon进行模型的简洁实现" class="headerlink" title="使用Gluon进行模型的简洁实现"></a>使用Gluon进行模型的简洁实现</h3><p>Gluon的rnn模块提供了循环神经网络的实现，</p>
<h3 id="时间反向传播"><a href="#时间反向传播" class="headerlink" title="时间反向传播"></a>时间反向传播</h3><p>不裁剪梯度时，模型将无法正常训练。我们将循环神经网络按时间步展开，从而得到模型变量与参数之间的依赖关系，并根据链式法则应用反向传播计算并存储梯度。</p>
<p><img src="/../../image/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/%E5%8F%8D%E5%90%91%E4%BC%A0%E6%92%AD.png" alt="反向传播"></p>
<p>每次迭代中，我们在依次计算完以上各个梯度后，会将它们存储起来，从而避免重复计算。同时，反向传播中的梯度计算可能会依赖变量的当前值，他们正是通过正向传播计算出来的。</p>
<h2 id="4、门控循环单元"><a href="#4、门控循环单元" class="headerlink" title="4、门控循环单元"></a>4、门控循环单元</h2><p>裁剪梯度可以应对梯度爆炸，但是没办法解决梯度衰减的问题。因此，循环神经网络在实际中很难捕捉时间序列中时间步距离较大的依赖关系。GRU门控循环神经网络通过可以学习的门来控制信息的流动。</p>
<h3 id=""><a href="#" class="headerlink" title=""></a></h3><h3 id="重置门和更新门"><a href="#重置门和更新门" class="headerlink" title="重置门和更新门"></a>重置门和更新门</h3><p>两者输入均为当前时间步输入Xt与上一时间步隐藏状态Ht-1，输出由激活函数为sigmoid函数的全连接层计算得到</p>
<p><img src="/../../image/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/%E9%87%8D%E7%BD%AE%E9%97%A8%E4%B8%8E%E6%9B%B4%E6%96%B0%E9%97%A8.png" alt="重置门与更新门"></p>
<h3 id="候选隐藏状态"><a href="#候选隐藏状态" class="headerlink" title="候选隐藏状态"></a>候选隐藏状态</h3><p>GRU将计算候选隐藏状态来辅助稍后的隐藏状态计算，先将当前时间步重置门的输出与上一时间步重置门隐藏状态做按元素乘法。重置门中元素值接近0，则意味重置对应隐藏状态元素为0，则丢弃上一时间步的隐藏状态；若近似1，则保留上一时间步隐藏状态。</p>
<p>之后与当前时间步的输出连结，再通过含t激活函数tanh的全连接层计算候选隐藏状态，其所有元素值域为[-1,1]</p>
<h3 id="-1"><a href="#-1" class="headerlink" title=""></a><img src="/../../image/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/%E5%80%99%E9%80%89%E9%9A%90%E8%97%8F%E7%8A%B6%E6%80%81.png" alt="候选隐藏状态"></h3><h3 id="隐藏状态"><a href="#隐藏状态" class="headerlink" title="隐藏状态"></a>隐藏状态</h3><p>最后，时间步的隐藏状态Ht的计算使用了当前时间步的更新门Zt来对上一时间步的隐藏状态Ht-1和当前时间步的候选隐藏状态Ht*来做组合：</p>
<p><img src="/../../image/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/%E9%9A%90%E8%97%8F%E7%8A%B6%E6%80%81.png" alt="隐藏状态"></p>
<p>在Gluon中可直接调用rnn模块中的GRU类来实现GRU门控循环。</p>
<h3 id="LSTM长短期记忆"><a href="#LSTM长短期记忆" class="headerlink" title="LSTM长短期记忆"></a>LSTM长短期记忆</h3><p>LSTM引入了三个门：输入门、遗忘门、输出门</p>
<p>在Gluon中可调用rnn模块中的LSTM类来实现长短期记忆。</p>
<h2 id="5、深度循环神经网络"><a href="#5、深度循环神经网络" class="headerlink" title="5、深度循环神经网络"></a>5、深度循环神经网络</h2><p>深度循环神经网络：目前为止介绍的循环神经网络只有一个单项的隐藏层，深度学习中通常会用到含多个隐藏层的循环神经网络，在该网络中，隐藏状态的信息不断传递至当前层的下一时间步和当前时间步的下一层。</p>
<p>双向循环神经网络：之前的介绍的神经网络模型都是假设当前时间步由前面的较早时间步的序列来决定，因此信息均通过隐藏状态向后传递。故双向神经网络通过增加从后往前传递信息的隐藏层来更灵活地处理这类信息。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2020/08/20/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2020/08/20/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/" class="post-title-link" itemprop="url">卷积神经网络</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2020-08-20 21:42:51" itemprop="dateCreated datePublished" datetime="2020-08-20T21:42:51+08:00">2020-08-20</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:23:34" itemprop="dateModified" datetime="2021-11-03T16:23:34+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/" itemprop="url" rel="index"><span itemprop="name">机器学习</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h2 id="1、二维卷积层"><a href="#1、二维卷积层" class="headerlink" title="1、二维卷积层"></a>1、二维卷积层</h2><p>二维卷积层：有高和宽两个空间维度用于处理图像数据，通常使用更直观的互相关运算来代替卷积运算。将输出与卷积核做互相关，并加上一个标量偏差来得到输出。</p>
<p>在训练模型时，通常先对卷积核初始化，然后不断迭代卷积核与偏差。使用corr2d函数来实现：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">corr2d</span><span class="params">(X,K)</span>:</span></span><br><span class="line">    h,w = K.shape</span><br><span class="line">    Y = nd.zeros((X.shape[<span class="number">0</span>] - h + <span class="number">1</span>,X.shape[<span class="number">1</span>] - w + <span class="number">1</span>))</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> range(Y.shape[<span class="number">0</span>]):</span><br><span class="line">        <span class="keyword">for</span> j <span class="keyword">in</span> range(Y.shape[<span class="number">1</span>]):</span><br><span class="line">            Y[i,j] = (X[ i:i+h , j: j+w]*K).sum()</span><br><span class="line">    <span class="keyword">return</span> Y</span><br></pre></td></tr></table></figure>

<p>在构造函数中声明了weight和bias，并在forward中利用corr2d函数来实现卷积核。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self,kernel_size,**kwargs)</span>:</span></span><br><span class="line">    super(Conv2D,self).__init__(**kwargs)</span><br><span class="line">    self.weight = self.params.get(<span class="string">'weight'</span>,shape=kernel_size)</span><br><span class="line">    self.bias = self.params.get(<span class="string">'bias'</span>,shape=(<span class="number">1</span>,))</span><br><span class="line">    </span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">forward</span><span class="params">(self,x)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> corr2d(x, self.weight.data()) + self.bias.data()</span><br></pre></td></tr></table></figure>

<p>一般通过数据学习得到核数组：首先构造一个卷积层，将其卷积核初始化为随机数组，并在每一次迭代中，使用平方误差来比较输出Y和卷积层的输出，并计算梯度来更新权重。</p>
<p>由于核数组都是学习出来的，所以卷积层使用互相关运算还是卷积运算都不影响模型预测时的输出。</p>
<p>特征图：输出可看作输入在空间维度上的表征。</p>
<p>影响X的前向计算的所有可能输入区域，叫做X的感受野。</p>
<h2 id="2、填充与步幅"><a href="#2、填充与步幅" class="headerlink" title="2、填充与步幅"></a>2、填充与步幅</h2><p>填充：在输入高、宽的两侧填充元素；用于增加输出的高、宽。</p>
<p>步幅：卷积窗口从输入窗口左上方开始，依次滑动，每次滑行的行数和列数称为步幅，用于减少输出的高、宽。</p>
<h2 id="3、多输入、多输出通道"><a href="#3、多输入、多输出通道" class="headerlink" title="3、多输入、多输出通道"></a>3、多输入、多输出通道</h2><p>1、多输入时，构造输入通道数与输入数据的通道数相同的卷积核，从而能够互相关运算，对每个通道做互相关，然后通过add_n函数来进行累加</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">corr2d_multi_in</span><span class="params">(X,K)</span>:</span></span><br><span class="line">    <span class="comment">#首先沿X和K的第0维（通道维）遍历，然后用*将结果列表变为add_n函数的位置参数（positional argument）来相加</span></span><br><span class="line">    <span class="keyword">return</span> nd.add_n(*[d2l.corr2d(x,k) <span class="keyword">for</span> x , k <span class="keyword">in</span> zip(X,K)]<span class="number">13</span></span><br></pre></td></tr></table></figure>

<p>2、卷积核输入、输出通道数为Ci、Co，高和宽分别为Kh和Kw。若希望有多输出时，为每个通道分别创建核数组Ci x Kh x Kw，并将其在输出通道维上连结，卷积核形状为C0 X Ci x Kh x Kw，互相关时每个输出通道结果由卷积核在该通道核数组与整个输入数组计算得来。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">corr2d_multi_in_out</span><span class="params">(X,K)</span>:</span></span><br><span class="line">    <span class="comment">#对K的第0维遍历，每次同输入X做互相关运算，所有结果用stack函数合并在一起</span></span><br><span class="line">    <span class="keyword">return</span> nd.stack(*[corr2d_multi_in(X,K) <span class="keyword">for</span> k <span class="keyword">in</span> k])</span><br></pre></td></tr></table></figure>



<h2 id="4、1-x-1-卷积层"><a href="#4、1-x-1-卷积层" class="headerlink" title="4、1 x 1 卷积层"></a>4、1 x 1 卷积层</h2><p>1 X 1 卷积主要发生在通道维上,输入与输出具有相同的高宽，输出中每个元素来自输入中在高、宽上相同位置的元素在不同通道之间的按权重累加。将通道维作为特征维，将高、宽上的元素当成数据样本，则其作用与全连接层等价。</p>
<p>1 X 1卷积层通常用来调整网络层之间的通道数，并控制模型复杂度。</p>
<h2 id="5、池化层"><a href="#5、池化层" class="headerlink" title="5、池化层"></a>5、池化层</h2><p>目的：为了缓解卷积层对位置的过度敏感性。</p>
<p>池化层：每次对输入数据的一个固定形状窗口中的元素计算输出，直接计算池化窗口内的元素最大值或平均值，也叫最大池化或者平均池化。p  X  q 池化层</p>
<p>同样池化层也能设置填充和步幅。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pool2d = nn.MxaPool2D(<span class="number">1</span>, kernel_size=(<span class="number">5</span>,<span class="number">3</span>),padding=(<span class="number">2</span>,<span class="number">1</span>),strides=(<span class="number">3</span>,<span class="number">4</span>))</span><br><span class="line"><span class="comment">#使用高、宽分别为5，3的卷积核；在高宽上的填充数分别为2，1；高宽上步幅分别为3，4</span></span><br></pre></td></tr></table></figure>

<p>在处理多通道输入数据时，池化层会对每个输入通道分别池化，而不是像卷积层那样将各通道输入按通道相加。因此，池化层的输出通道数与输入通道数相等。</p>
<h2 id="6、卷积神经网络"><a href="#6、卷积神经网络" class="headerlink" title="6、卷积神经网络"></a>6、卷积神经网络</h2><p>单隐藏层的多层感知机分类图像：将图像像素逐行展开，得到长为28*28=784的向量，并输入进全连接层中。局限性:1、同一列像素相隔远；2、对于大尺寸图像，全连接层可能导致模型过大。</p>
<p>卷积层如何解决：1、保留输入形状，使像素在高、宽两个方向的相关性均能被有效识别；2、通过滑动窗口将同一卷积核与不同位置的输入重复计算，从而避免参数尺寸过大。</p>
<h3 id="LeNet模型"><a href="#LeNet模型" class="headerlink" title="LeNet模型"></a>LeNet模型</h3><p>分为卷积层块与全连接层块。</p>
<p>卷积层基本单位：卷积层+最大池化层。卷积层用于识别图像中空间模式，最大池化层用于降低卷积层对位置的敏感性。</p>
<p>卷积层输出形状为（批量大小，通道，高，宽）；当其输出传入全连接层块时，全连接层会将小批量中的每个样本变平。形状将改变为二维，第一维是小批量中的样本，第二维是每个样本变平后的向量表示，且向量长度为通道*高 *宽。</p>
<h3 id="AlexNet模型"><a href="#AlexNet模型" class="headerlink" title="AlexNet模型"></a>AlexNet模型</h3><p>1、包含8层变换，其中有5层卷积和2层全连接隐藏层，全连接输出个数为4096；</p>
<p>2、将激活函数由sigmoid改成了更简单的Relu，在不同参数初始化方法下使模型更容易训练，且在正区间的梯度恒为1;</p>
<p>3、利用丢弃法来控制全连接层的模型复杂度；</p>
<p>4、引入大量的图像增广，从而进一步扩充数据集来缓解过拟合；</p>
<p>5、利用了更多的卷积层和更大的参数空间来拟合大规模数据集ImageNet，是浅层网络与深度神经网络的分界线。</p>
<h3 id="VGG模型"><a href="#VGG模型" class="headerlink" title="VGG模型"></a>VGG模型</h3><p>VGG提出了可以通过重复使用简单的基础块来构建深度模型的思路；其组成规律是：连续使用数个相同的填充为1、窗口形状为3X3的卷积层后接上一个步幅为2、窗口形状为2X2的最大池化层。卷积层保持输入的高、宽不变，而池化层则对其减半。使用vgg_block函数来实现基础的VGG卷积层模块，可指定卷积层数量num_convs和输出通道数num_channels。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">vgg_block</span><span class="params">(num_convs, num_channels)</span>:</span></span><br><span class="line">    blk = nn.Sequential()</span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> range(num_convs):</span><br><span class="line">        blk.add(nn.Conv2D(num_channels, kernel_size=<span class="number">3</span>,padding=<span class="number">1</span>,activation=<span class="string">'relu'</span>))</span><br><span class="line">    blk.add(nn.MaxPool2D(pool_size=<span class="number">2</span>, strides=<span class="number">2</span>))</span><br><span class="line">    <span class="keyword">return</span> blk</span><br></pre></td></tr></table></figure>

<p>VGG由卷积模块后接全连接层模块构成，卷积层串联数个vgg_block，其超参数由变量conv_arch定义，指定了VGG块中卷积层个数与输出通道数。下面构造VGG，5个卷积块，前2块用单卷积层，后3块用双卷积层。第一块输出通道为64，之后每次输出通道数翻倍，直至512。总共8个卷积，3个全连接，因此称为VGG-11。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">conv_arch = ((<span class="number">1</span>,<span class="number">64</span>),(<span class="number">1</span>,<span class="number">128</span>),(<span class="number">2</span>,<span class="number">256</span>),(<span class="number">2</span>,<span class="number">512</span>),(<span class="number">2</span>,<span class="number">512</span>))</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">vgg</span><span class="params">(conv_arch)</span>:</span></span><br><span class="line">    net = nn.Sequential()</span><br><span class="line">    <span class="comment">#卷积层部分</span></span><br><span class="line">    <span class="keyword">for</span> (num_convs, num_channels) <span class="keyword">in</span> conv_arch:</span><br><span class="line">        net.add(vgg_block(num_convs, num_channels))</span><br><span class="line">    <span class="comment">#全连接层部分</span></span><br><span class="line">    net.add(nn.Dense(<span class="number">4096</span>, activation=<span class="string">'relu'</span>), nn.Dropout(<span class="number">0.5</span>), </span><br><span class="line">            nn.Dense(<span class="number">4096</span>, activation=<span class="string">'relu'</span>), nn.Dropout(<span class="number">0.5</span>), </span><br><span class="line">            nn.Dense(<span class="number">10</span>))</span><br><span class="line">    <span class="keyword">return</span> net</span><br></pre></td></tr></table></figure>



<h3 id="NIN模型"><a href="#NIN模型" class="headerlink" title="NIN模型"></a>NIN模型</h3><p>上述模型共通处均是：先以卷积层构成的模块充分抽取空间特征，再以全连接层构成的模块来输出分类结果。</p>
<p>NIN则提出了另外一个思路：串联多个由卷积层和全连接层构成的小网络来构建深层网络。</p>
<p>卷积层通常输入、输出：四维数组（样本、通道、高、宽）；全连接层输入输出：二维数组（样本、特征）。故利用1 X 1卷积层来代替全连接层，从而使空间信息自然传递至后面层。</p>
<p>NIN块：由一个卷积层和两个充当全连接层的1 X 1卷积层串联而来，可自由设置第一个卷积层超参数。</p>
<p><img src="/../../image/%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/NIN.png" alt="NIN"></p>
<p>NIN设计:除了使用NIN块以外，NIN去掉了AlexNet最后的3个全连接层，并使用输出通道数等于标签类别数的NIN块，然后使用全局平均池化层对每个通道中所有元素求平均，并直接进行分类。NIN这个设计的好处：显著减少模型参数尺寸，从而缓解过拟合。</p>
<h3 id="GoogLeNet模型"><a href="#GoogLeNet模型" class="headerlink" title="GoogLeNet模型"></a>GoogLeNet模型</h3><p>GoolLeNet中的基础卷积块为Inception块，有4条并行的线路，前3条线路使用窗口分别为1X1,3X3,5X5的卷积层来抽取不同的空间尺寸下的信息，且中间2条线路会对输入先做1X1卷积来减少输入通道数，以降低模型复杂度；第四条线路则用3X3池化层后接1X1卷积层来改变通道数；且均使用了合适的填充来使输入、输出高宽一致。</p>
<p><img src="/../../image/%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/GooLeNet.png" alt="GooLeNet"></p>
<p>GooLeNet跟VGG一样，在主体卷积部分使用5个模块，每个模块之前使用步幅为2的3 X 3最大池化层来减小输出高宽。</p>
<p>模块一：使用一个64通道的7X7卷积层；</p>
<p>模块二：使用2个卷积层，64通道的1X1卷积层，然后是将通道增大3倍的3X3卷积层，对应Inception的线路二；</p>
<p>模块三：串联2个完整的Inception块；</p>
<p>模块四：串联5个Inception块；</p>
<p>模块五：2个Inception块，其后紧跟输出层，故同NIN一样使用全局平均池化层来将每个通道的高、宽变成1</p>
<h2 id="7、批量归一化"><a href="#7、批量归一化" class="headerlink" title="7、批量归一化"></a>7、批量归一化</h2><p>数据标准化处理：任一特征在数据集所有样本上的均值为0、标准差为1，可以使各个特征的分布相近。对于浅层模型，数据标准化预处理已经足够了，但对于深层网络，模型参数更新仍容易造成剧烈变化。</p>
<p>批量归一化层（batch normalization）：为应对深层模型的挑战，在训练时，利用小批量上的均值和标准差，不断调整神经网络中间输出，从而使整个神经网络在各层的中间输出数值更稳定。</p>
<h3 id="全连接层的批量归一化"><a href="#全连接层的批量归一化" class="headerlink" title="全连接层的批量归一化"></a>全连接层的批量归一化</h3><p>对于全连接层：将批量归一化层放在全连接层的仿射交换与激活函数之间。</p>
<h3 id="对卷积层做批量归一化"><a href="#对卷积层做批量归一化" class="headerlink" title="对卷积层做批量归一化"></a>对卷积层做批量归一化</h3><p>对于卷积层：批量归一化发生在卷积计算之后、应用激活函数之前。</p>
<h2 id="8、残差网络ResNet"><a href="#8、残差网络ResNet" class="headerlink" title="8、残差网络ResNet"></a>8、残差网络ResNet</h2><p>实践中，添加过多层后训练误差往往不降反升，即使利用批量归一化使训练深层模型更加容易，该问题仍存在。</p>
<p>残差块：当理想映射f(x)极接近恒等映射时，残差映射也易于捕捉恒等映射的细微波动。</p>
<p><img src="/../../image/%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/ResNet.png" alt="ResNet"></p>
<p>ResNet沿用了VGG全3X3卷积层的设计，残差块中首先由2个相同输出通道数的3X3卷积层，每个卷积层之后接一个批量归一化层和RELU激活函数，然后将输入跳过这2个卷积运算后再加在最后的RELU激活函数前。这样设计要求2个卷积层的输入、输出形状一样，从而可以相加。</p>
<p>ResNet的前两层跟GoogLeNet一样，在输出通道为64、步幅为2的7X7卷积层后接步幅为2的3X3最大池化层。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">net = nn.Sequential()</span><br><span class="line">net.add(nn.Conv2D(<span class="number">64</span>,kernel_size=<span class="number">7</span>,strides=<span class="number">2</span>,padding=<span class="number">3</span>),</span><br><span class="line">       nn.BatchNoem(),nn.Activation(<span class="string">'relu'</span>),</span><br><span class="line">       nn.MaxPool2D(pool_size=<span class="number">3</span>,strides=<span class="number">2</span>,padding=<span class="number">1</span>))</span><br></pre></td></tr></table></figure>

<p>不同在于其每个卷积层后增加的批量归一层,GoogLeNet在后面会接4个Inception组成的模块，而ResNet则使用4个由残差块组成的模块，每个模块使用若干个同样输出通道数的残差块。我们用resnet_block函数来实现：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">resnet_block</span><span class="params">(num_channels, num_residuals, first_block=False)</span>:</span></span><br><span class="line">    blk = nn.Sequential()</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> range(num_residuals):</span><br><span class="line">        <span class="keyword">if</span> i == <span class="number">0</span> <span class="keyword">and</span> <span class="keyword">not</span> first_block:</span><br><span class="line">            blk.add(Residual(num_channels, use_1x1conv=<span class="literal">True</span>, strides=<span class="number">2</span>))</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            blk.add(Residual(num_channels))</span><br><span class="line">    <span class="keyword">return</span> blk</span><br></pre></td></tr></table></figure>

<p>接着使用ResNet加入所有残差块，这里每个模块使用2个残差块</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">net.add(resnet_block(<span class="number">64</span>, <span class="number">2</span>, first_block=<span class="literal">True</span>),</span><br><span class="line">       resnet_block(<span class="number">128</span>,<span class="number">2</span>),</span><br><span class="line">       resnet_block(<span class="number">256</span>,<span class="number">2</span>),</span><br><span class="line">       resnet_block(<span class="number">512</span>,<span class="number">2</span>))</span><br><span class="line"></span><br><span class="line">net.add(nn.GlobalAvgPool2D(), nn.Dense(<span class="number">10</span>))</span><br><span class="line"><span class="comment">#加入全局平均池化层后接上全连接层输出</span></span><br></pre></td></tr></table></figure>



<h2 id="9、稠密连接网络DenseNet"><a href="#9、稠密连接网络DenseNet" class="headerlink" title="9、稠密连接网络DenseNet"></a>9、稠密连接网络DenseNet</h2><p><img src="/../../image/%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/DenseNet.png" alt="DenseNet"></p>
<p>DenseNet主要构建模块时稠密块和过渡层，前者定义了输入、输出是如何连结的，后者则用来控制通道数，使之不过大。</p>
<p>由于每个稠密块都会带来通道数的增加，使用过多则会带来过于复杂的模型，过度层用来控制模型复杂度。它通过1X1卷积层来减少通道数，并使用步幅为2的的平均池化层减半高、宽，从而进一步降低模型复杂度。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2020/08/19/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%88%9D%E8%A7%81/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2020/08/19/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%88%9D%E8%A7%81/" class="post-title-link" itemprop="url">MXNET机器学习初见</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2020-08-19 23:28:46" itemprop="dateCreated datePublished" datetime="2020-08-19T23:28:46+08:00">2020-08-19</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:23:24" itemprop="dateModified" datetime="2021-11-03T16:23:24+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/" itemprop="url" rel="index"><span itemprop="name">机器学习</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h2 id="1、基础知识"><a href="#1、基础知识" class="headerlink" title="1、基础知识"></a>1、基础知识</h2><p>NDArray、NumPy的广播机制：数组维度不同，后缘维度的轴长（从末尾算起的维度）相同；（4，3）+（3，）；（3，4，2）+（4，2）   2、数组维度相同，其中有个轴为1；（4，3）+（4，1）：在1轴上广播扩展。</p>
<p>NDArray,NumPy的相互变换：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">P = np.ones((<span class="number">2</span>,<span class="number">3</span>))</span><br><span class="line">D = nd.array(P)</span><br><span class="line">D.asnumpy()</span><br></pre></td></tr></table></figure>

<p>自动求梯度（gradient）MXNET中使用autograd模块自动求梯度：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">x = nd.arange(<span class="number">4</span>).reshape((<span class="number">4</span>,<span class="number">1</span>))</span><br><span class="line">x.attach_grad()  <span class="comment">#申请内存</span></span><br><span class="line"><span class="keyword">with</span> autograd.record():</span><br><span class="line">    y = <span class="number">2</span> * nd.dot(x.T,x) <span class="comment">#若y为标量，贼会默认对y元素求和后，求关于X的梯度</span></span><br><span class="line">y.backward()</span><br></pre></td></tr></table></figure>



<p>uniform:均匀分布采样；normal:正态分布采样；poisson:泊松分布采样。</p>
<h2 id="2、线性回归"><a href="#2、线性回归" class="headerlink" title="2、线性回归"></a>2、线性回归</h2><p>损失函数：平方函数，平方损失；在模型训练中，希望找到一组模型参数为w1,w2,b使得训练样本平均损失最小。</p>
<p>解析解：误差最小化问题的解刚好可用数学公式表达出来；大多数为数值解，只能利用优化算法有限次迭代模型参数，从而尽可能降低损失函数的值。</p>
<p>全连接层：又名稠密层，输出层中的神经元与输入层中的各个输入完全连接；</p>
<p>矢量计算比标量逐个相加更加省时间，故往往利用矢量矩阵运算来实现深度学习；</p>
<p>优化算法：小批量随机梯度下降：批量大小batch size,学习率 lr 均为超参数，为人为设定并非模型学习出来的，</p>
<p>调参：通过反复试错来寻找合适的超参数，</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">sgd</span><span class="params">(params,lr,batch_size)</span>:</span> <span class="comment">#sgd函数实现小批量随机梯度下降算法</span></span><br><span class="line">  <span class="keyword">for</span> param <span class="keyword">in</span> params:</span><br><span class="line">    param[:] = param - lr * param.grad / batch_size</span><br></pre></td></tr></table></figure>

<p>在一个迭代周期epoch中，将完整遍历一遍data_iter函数，并对训练数据集中所有样本都使用一次。</p>
<h3 id="Gluon简洁实现："><a href="#Gluon简洁实现：" class="headerlink" title="Gluon简洁实现："></a>Gluon简洁实现：</h3><p>1、提供data包来读取数据：</p>
<p>2、提供大量预定义层，nn模块：neural networks：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> mxnet.gluon <span class="keyword">import</span> nn</span><br><span class="line">net = nn.Sequential()  <span class="comment">#Sequential实例是串联各层的容器，依次添加层，每一层一次计算并作为下一层输入</span></span><br><span class="line">net.add(nn.Dense(<span class="number">1</span>)) <span class="comment">#Dense全连接层，GLUON无须指定各层形状，模型会自动推断</span></span><br></pre></td></tr></table></figure>

<p>3、利用init模块来实现模型参数初始化的各种方法：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> mxnet.gluon <span class="keyword">import</span> init</span><br><span class="line">net.initialize(init.Normal(sigma=<span class="number">0.01</span>)) <span class="comment">#均值为0，标准差0.01的正态分布</span></span><br></pre></td></tr></table></figure>

<p>4、定义损失函数：利用loss模块：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> mxnet.gluon <span class="keyword">import</span> loss <span class="keyword">as</span> gloss</span><br><span class="line">loss = gloss.L2Loss() <span class="comment">#平方损失又是L2范数损失</span></span><br></pre></td></tr></table></figure>

<p>5、定义优化算法：创建Trainer实例，以sgd作为优化算法，用来迭代net实例所有通过add函数嵌套的层包含的全部参数，可通过collect_params函数获取。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">trainer = gluon.Trainer(net.collect_params(),<span class="string">'sgd'</span>,&#123;<span class="string">'learning_rate'</span>:<span class="number">0.03</span>&#125;)</span><br></pre></td></tr></table></figure>

<p>6、训练模型：调用Trainer实例的step函数来迭代模型参数，按sgd的定义，在step中指明批量大小，从而对样本梯度求平均。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">num_epochs = <span class="number">3</span></span><br><span class="line"><span class="keyword">for</span> epoch <span class="keyword">in</span> range(<span class="number">1</span>,num_epochs + <span class="number">1</span>):</span><br><span class="line">    <span class="keyword">for</span> X,y <span class="keyword">in</span> data_iter:</span><br><span class="line">        <span class="keyword">with</span> autograd.record():</span><br><span class="line">            l = loss(net(X),y)</span><br><span class="line">        l.backward()</span><br><span class="line">        trainer.step(batch_size)</span><br><span class="line">    l = loss(net(features), labels)</span><br><span class="line">    print(<span class="string">'epoach %d,loss: %f'</span>%(epoch,l.mean().asnumpy()))</span><br></pre></td></tr></table></figure>



<h2 id="3、softmax回归"><a href="#3、softmax回归" class="headerlink" title="3、softmax回归"></a>3、softmax回归</h2><p>模型输出为图像类别这种离散值时，使用softmax回归，其输出单元从一个变成了多个，且引入了softmax运算使输出更加适合离散值的预测和训练。</p>
<p>sofemax回归模型：将输出特征与权重做线性叠加，输出值个数等于标签里的类别数。例：有4种特征（4个像素的图片）和3种输出动物类别，则权重包含12个标量（带下标w)、偏差包含3个标量（带下标b）。每个On计算都依赖所有输入，故为全连接层。</p>
<p>softmax计算：直接用最高On作为预测输出，有2个问题。1、输出值范围不定，难以直观判断；2、误差难以衡量。softmax运算符可以解决，即归一化,但softmax运算不改变预测类别输出。<br>$$<br>y1 = exp(O1)/exp(O1)+exp(O2)+exp(O3)<br>$$<br>交叉熵函数：使用更适合衡量分布差异的测量函数，只关心对正确类别的预测概率，<br>$$<br>H(yi,y<em>i) = -Σ(yilogy</em>i)<br>$$<br>交叉熵损失函数，最小化其等价于最大化训练数据集所有标签类别的联合预测概率。<br>$$<br>l(o) =1/n * Σ(yilogy*i)<br>$$<br>图像分类数据集：Fashion-MNIST</p>
<p>Gluon的DataLoader中可用多进程来加速数据读取；通过ToTensor实例将图像数据从unit8格式变换成32位浮点数格式，并除以255使得所有像素均在0至1之间。</p>
<h3 id="Gluon简洁实现"><a href="#Gluon简洁实现" class="headerlink" title="Gluon简洁实现"></a>Gluon简洁实现</h3><p>1、导入模块并获取函数</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">%matplotlib inline</span><br><span class="line"><span class="keyword">import</span> d2zlzh <span class="keyword">as</span> d2l</span><br><span class="line"><span class="keyword">from</span> mxnet <span class="keyword">import</span> gluon, init</span><br><span class="line"><span class="keyword">from</span> mxnet.gluon <span class="keyword">import</span> loss <span class="keyword">as</span> gloss, nn</span><br><span class="line">batch_size = <span class="number">256</span> <span class="comment">#批量大小设置</span></span><br><span class="line">train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size)</span><br></pre></td></tr></table></figure>

<p>2、定义和初始化模型</p>
<p>添加输出为10的全连接层，并用均值为0、标准差为0.01的正态分布随机初始化模型的权重参数。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">net = nn.Sequential()</span><br><span class="line">net.add(nn.Dense(<span class="number">10</span>))</span><br><span class="line">net.initialize(init.Normal(sigma=<span class="number">0.01</span>))</span><br></pre></td></tr></table></figure>

<p>3、同时定义softmax和交叉熵损失函数，使数值稳定性更好，使用Gluon提供的函数。</p>
<p>定义优化算法：使用学习率为0.1的小批量随机梯度下降算法。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">loss = gloss.SoftmaxCrossEntropyLoss()</span><br><span class="line">trainer = gluon.Trainer(net.collect_params()，<span class="string">'sgd'</span>,&#123;<span class="string">'learining_rate'</span>:<span class="number">0.1</span>&#125;)</span><br></pre></td></tr></table></figure>

<p>4、使用上一节定义的训练函数来训练模型：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">num_epochs = <span class="number">5</span></span><br><span class="line">d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,batch_size,<span class="literal">None</span>,<span class="literal">None</span>,trainer)</span><br></pre></td></tr></table></figure>



<h2 id="4、多层感知机"><a href="#4、多层感知机" class="headerlink" title="4、多层感知机"></a>4、多层感知机</h2><p>深度学习主要关注多层模型，以多层感知机NLP（multilayer perceptron）为例。在单层网络的基础上引入了隐藏层hidden layer，但多个仿射线性变换叠加仍然是线性仿射，需引入非线性函数，该函数被称为激活函数</p>
<p>RELU函数：RELU(x) = max(x,0)</p>
<p>sigmoid函数：sigmoid(x) = 1/[1+exp(-x)]</p>
<p>sigmoid函数的导数：sigmoid(x)(1-sigmoid(x))</p>
<p>tanh（双曲正切）函数：[1- exp(-2x)]/[1+exp(-2x)]</p>
<p>tanh函数的导数：1 - [tanh(x)]^2</p>
<h3 id="Gluon的简洁实现"><a href="#Gluon的简洁实现" class="headerlink" title="Gluon的简洁实现"></a>Gluon的简洁实现</h3><p>1、导入包与模块，并定义模型,，多加一个全连接作为隐藏层，单元数为256，用RELU作为激活函数。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> d2zlzh <span class="keyword">as</span> d2l</span><br><span class="line"><span class="keyword">from</span> mxnet <span class="keyword">import</span> gluon, init</span><br><span class="line"><span class="keyword">from</span> mxnet.gluon <span class="keyword">import</span> loss <span class="keyword">as</span> gloss, nn</span><br><span class="line">net = nn.Sequential()</span><br><span class="line">net.add(nn.Dense(<span class="number">256</span>,activation = <span class="string">'relu'</span>),nn.Dense(<span class="number">10</span>))</span><br><span class="line">net.initialize(init.Normal(sigma = <span class="number">0.01</span>))</span><br></pre></td></tr></table></figure>

<p>2、使用与softmax回归几乎相同的步骤来读取数据并训练模型,学习率为0.5</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">batch_size = <span class="number">256</span> <span class="comment">#批量大小设置</span></span><br><span class="line">train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size)</span><br><span class="line">oss = gloss.SoftmaxCrossEntropyLoss()</span><br><span class="line">trainer = gluon.Trainer(net.collect_params()，<span class="string">'sgd'</span>,&#123;<span class="string">'learining_rate'</span>:<span class="number">0.5</span>&#125;)</span><br><span class="line">num_epochs = <span class="number">5</span> <span class="comment">#迭代周期num_epochs指的是要循环学习几次</span></span><br><span class="line">d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,batch_size,<span class="literal">None</span>,<span class="literal">None</span>,trainer)</span><br></pre></td></tr></table></figure>



<h2 id="5、模型选择与拟合问题"><a href="#5、模型选择与拟合问题" class="headerlink" title="5、模型选择与拟合问题"></a>5、模型选择与拟合问题</h2><p>训练误差：模型在训练集上表现出来的误差；</p>
<p>泛化误差：任意一个测试数据样本上表现出的误差的期望；</p>
<p>使用验证数据集来进行模型选择：预留一部分在训练、测试数据集之外的数据来进行模型选择。K折交叉验证：将原始数据分成K个不重合的子数据集，做K次模型训练和验证，每一次用一个子数据集来验证模型，其他用于训练模型。最后对这K次结果分别求平均。</p>
<p>欠拟合：模型无法得到较低的训练误差。</p>
<p>过拟合：模型训练误差远小于其在测试集上误差；模型越复杂、训练集越小越容易过拟合。</p>
<h2 id="6、权重衰减、丢弃法来处理过拟合"><a href="#6、权重衰减、丢弃法来处理过拟合" class="headerlink" title="6、权重衰减、丢弃法来处理过拟合"></a>6、权重衰减、丢弃法来处理过拟合</h2><p>权重衰减：等价于L2范数正则化，通过为模型损失函数添加惩罚项，使学到的模型参数值较小。</p>
<p>L2惩罚项指的是：模型权重参数的每一个元素的平方和与一个正的常数的乘积。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">l2_penalty</span><span class="params">(w)</span>:</span></span><br><span class="line">  <span class="keyword">return</span> (w**<span class="number">2</span>).sum() / <span class="number">2</span></span><br></pre></td></tr></table></figure>

<h3 id="权重衰减Gluon简洁实现："><a href="#权重衰减Gluon简洁实现：" class="headerlink" title="权重衰减Gluon简洁实现："></a>权重衰减Gluon简洁实现：</h3><p>构造Trainer实例时通过wd参数来指定权重衰减超参数，默认下会对权重、偏差同时衰减。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#对权重参数衰减，权重名称一般以weight结尾</span></span><br><span class="line">trainer_w = gluon.Trainer(net.collect_params(<span class="string">'.*weight'</span>),<span class="string">'sgd'</span>,&#123;<span class="string">'learning_rate'</span>:lr,<span class="string">'wd'</span>:wd&#125;)</span><br><span class="line"><span class="comment">#不对偏差参数衰减，偏差名称一般以bias结尾</span></span><br><span class="line">trainer_b = gluon.Trainer(net.collect_params(<span class="string">'.*bias'</span>),<span class="string">'sgd'</span>,&#123;<span class="string">'learning_rate'</span>:lr&#125;)</span><br></pre></td></tr></table></figure>



<p>丢弃法：隐藏单元有一定的概率P被丢弃掉，丢弃概率是丢弃法的超参数。具体而言，随机变量$为0和1的概率分别为P和1-P。</p>
<p>定义dropout函数，以drop_prob的概率丢弃NDArray输入X中的元素。</p>
<h3 id="丢弃法Gluon简洁实现："><a href="#丢弃法Gluon简洁实现：" class="headerlink" title="丢弃法Gluon简洁实现："></a>丢弃法Gluon简洁实现：</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">net = nn.Sequential()</span><br><span class="line">net.add(nn.Dense(<span class="number">256</span>,activation = <span class="string">'relu'</span>),nn.Dropout(drop_prob1),  <span class="comment">#在第一个全连接层后添加丢弃层</span></span><br><span class="line">       nn.Dense(<span class="number">256</span>,activation = <span class="string">'relu'</span>),nn.Dropout(drop_prob2),nn.Dense(<span class="number">10</span>))</span><br><span class="line">net.initialize(init.Normal(sigma = <span class="number">0.01</span>))</span><br></pre></td></tr></table></figure>



<h2 id="7、反向传播"><a href="#7、反向传播" class="headerlink" title="7、反向传播"></a>7、反向传播</h2><p>反向传播：指计算神经网络梯度的方法，依据链式法则，其梯度计算可能依据各变量的当前值，而这些变量的当前值是通过正向传播计算得到的。</p>
<p>正向传播的计算可能依赖于模型参数的当前值，而参数是在反向传播的梯度计算后通过优化算法迭代的。</p>
<p>模型参数初始化完成后，交替地进行正向传播和反向传播，并根据反向传播计算的梯度迭代模型参数。</p>
<p>梯度衰减、爆炸：由于层数过大时，输出呈幂次爆炸增长，故梯度爆炸或梯度消失。</p>
<h2 id="8、深度学习计算原理细节"><a href="#8、深度学习计算原理细节" class="headerlink" title="8、深度学习计算原理细节"></a>8、深度学习计算原理细节</h2><h3 id="1、基于Block类的模型构建"><a href="#1、基于Block类的模型构建" class="headerlink" title="1、基于Block类的模型构建"></a>1、基于Block类的模型构建</h3><p>Block类是nn模块里提供的一个模型构造类，继承Block类来构造多层感知机，重载init函数与forward函数，分别用于创建模型参数与定义前向计算。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MLP</span><span class="params">(nn.Block)</span>:</span> <span class="comment">#声明带有模型参数的层，声明2个全连接层</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, **kwargs)</span>:</span></span><br><span class="line">        <span class="comment">#调用父类构造函数来进行必要初始化。</span></span><br><span class="line">        super(MLP,self).__init__(**kwargs)</span><br><span class="line">        self.hidden = nn.Dense(<span class="number">256</span>, activation=<span class="string">'relu'</span>) <span class="comment">#隐藏层</span></span><br><span class="line">        self.output = nn.Dense(<span class="number">10</span>) <span class="comment">#输出层</span></span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">forward</span><span class="params">(self,x)</span>:</span> <span class="comment">#定义模型的前向计算，即如何根据输入x计算返回所需的模型输出</span></span><br><span class="line">        <span class="keyword">return</span> self.output(self.hidden(x))</span><br></pre></td></tr></table></figure>

<p>无需定义反向传播，系统将自动求梯度而生成反向传播所需的backward函数</p>
<p>实例化MLP类得到net，并传入输入数据X并做一次前向计算。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">X = nd.random.uniform(shape=(<span class="number">2</span>,<span class="number">20</span>))</span><br><span class="line">net = MLP()</span><br><span class="line">net.initialize()</span><br><span class="line">net(X)</span><br></pre></td></tr></table></figure>

<h3 id="2、构建一个继承于Block类的继承类"><a href="#2、构建一个继承于Block类的继承类" class="headerlink" title="2、构建一个继承于Block类的继承类"></a>2、构建一个继承于Block类的继承类</h3><p>提供add函数来逐一添加串联的Block子类实例，而模型的前向计算就是将这些实例按顺序逐一计算。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MySequential</span><span class="params">(nn.Block)</span>:</span></span><br><span class="line">    def__init__(self,**kwargs):</span><br><span class="line">        super(MySequential,self).__init__(**kwargs)</span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(self,block)</span>:</span></span><br><span class="line">        <span class="comment">#block为Block实例，当MySequential实例调用initialize函数时，系统会自动对其所有成员初始化</span></span><br><span class="line">        self._children[block.name] = block</span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">forward</span><span class="params">(self,x)</span>:</span></span><br><span class="line">        <span class="comment">#OrderedDict保证按添加顺序遍历成员</span></span><br><span class="line">        <span class="keyword">for</span> block <span class="keyword">in</span> self._children.values():</span><br><span class="line">            x = block(x)</span><br><span class="line">         <span class="keyword">return</span> x</span><br></pre></td></tr></table></figure>



<h3 id="3、自定义初始化模型参数"><a href="#3、自定义初始化模型参数" class="headerlink" title="3、自定义初始化模型参数"></a>3、自定义初始化模型参数</h3><p>对于Sequential类构造的神经网络，可通过方括号[]来访问网络的任一层。同时Sequential实例中含模型参数的层，可通过Block类的params属性来访问该层包含的参数。</p>
<p>共享模型参数：在利用Block类中的forward函数里多次调用一个层来计算。或者，在构造层时指定特定的参数，若不同层使用同一份参数，则它们会在前向、反向时均共享相同的参数。</p>
<p>延后初始化：只有将形状是（，）的输入X传进网络做前向计算net(X)时，系统才能推断出该层权重参数形状为（，），此时才能真正地开始初始化参数。</p>
<p>避免延后初始化：1、要对已初始化的模型重新初始化时，由于参数形状不会变化，能够立即重新初始化；2、创建层的时候指定了它的输入个数，故系统不需要额外信息来推测参数形状。</p>
<h3 id="4、自定义层"><a href="#4、自定义层" class="headerlink" title="4、自定义层"></a>4、自定义层</h3><h3 id="5、读取与存储"><a href="#5、读取与存储" class="headerlink" title="5、读取与存储"></a>5、读取与存储</h3><p>将内存中训练好的模型参数存储在硬盘上，供后续读取使用。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2020/08/19/C++/C-%E5%9B%9B%E5%A4%A9%E5%85%A5%E9%97%A8/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2020/08/19/C++/C-%E5%9B%9B%E5%A4%A9%E5%85%A5%E9%97%A8/" class="post-title-link" itemprop="url">C++初见</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2020-08-19 18:14:31" itemprop="dateCreated datePublished" datetime="2020-08-19T18:14:31+08:00">2020-08-19</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:18:02" itemprop="dateModified" datetime="2021-11-03T16:18:02+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/C/" itemprop="url" rel="index"><span itemprop="name">C++</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="基础架构"><a href="#基础架构" class="headerlink" title="基础架构"></a>基础架构</h1><h2 id="1、数组与函数的关系"><a href="#1、数组与函数的关系" class="headerlink" title="1、数组与函数的关系"></a>1、数组与函数的关系</h2><p>数组与函数并无本质区别，均是一种映射的形式。变量函数，指针数组。</p>
<p>数组：展开的函数，关键在于指针变化，查询时间快。</p>
<p>函数：压缩的数组，关键在于值传递，用INDEX来定位，所用空间小。</p>
<p>算法中的时空互换逻辑，时间复杂度与空间复杂度可以一定程度互换。</p>
<h2 id="2、C-的编程逻辑"><a href="#2、C-的编程逻辑" class="headerlink" title="2、C++的编程逻辑"></a>2、C++的编程逻辑</h2><p>面向过程：自顶向下的编程，性能高，但需要处理实现性能的每一个细节，难以复用、扩展。</p>
<p>面向对象：抽象化事物，建立模型。</p>
<p>函数式编程：强调将计算过程分解成可复用的函数，MAP方法。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">auto add &#x3D; [](int a,int b) -&gt; int &#123;return a+b&#125;;</span><br></pre></td></tr></table></figure>

<p>泛型编程：STL，编写完全一般化并可重复使用的算法，其效率与针对某特定数据类型而设计的算法相同；泛型：在多种数据类型上皆可操作；将算法与数据结构完全分离。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">template &lt;typename T,typename U&gt;</span><br><span class="line">auto add(T a, U, b) -&gt; decltype(a+b)</span><br><span class="line">return a + b</span><br></pre></td></tr></table></figure>



<h2 id="3、程序执行的底层"><a href="#3、程序执行的底层" class="headerlink" title="3、程序执行的底层"></a>3、程序执行的底层</h2><h3 id="C源码—编译—》对象文件—链接—》可执行程序"><a href="#C源码—编译—》对象文件—链接—》可执行程序" class="headerlink" title="C源码—编译—》对象文件—链接—》可执行程序"></a>C源码—编译—》对象文件—链接—》可执行程序</h3><p>编译时：语法检查，一个源码生成一个目标文件</p>
<p>对象文件：存储各种各样定义</p>
<p>链接：需将所有对象文件定义捏合在一起</p>
<p>定义：函数具体的实现过程在这（有地址空间的为定义）</p>
<p>声明：说有这样一个东西（无地址空间为声明），作用于编译阶段用于语法检查，在调用函数时做语法检查，仅包含函数传入参数与返回值，并不关心函数内部</p>
<p>nm -C main.O </p>
<p>查看main.O该对象文件内容，main.O中printf由系统库实现，add由自定库实现</p>
<p>为何要分开定义与声明？ </p>
<h3 id="h头文件—》放置声明-源文件—》放置定义"><a href="#h头文件—》放置声明-源文件—》放置定义" class="headerlink" title=".h头文件—》放置声明   源文件—》放置定义"></a>.h头文件—》放置声明   源文件—》放置定义</h3><p>把定义放在头文件往往会产生bug</p>
<p>apt-get install vim.deb; ctrl +t 打开中断</p>
<p>凡是未定义（undefined）、冲突（duplicate:符号定义有2个）的错误—》一般是链接阶段的错误；</p>
<h2 id="4、google测试框架"><a href="#4、google测试框架" class="headerlink" title="4、google测试框架"></a>4、google测试框架</h2><p>要实现第三方模块功能的引入—》引入头文件 .h</p>
<p>其定义压缩在一起生成了库文件：静态链接库： .lib</p>
<p>IDE：集成开发环境=文本(vim,gcc) + 编译(g++) + 调试(gdb,lldb)</p>
<h3 id="添加谷歌测试框架"><a href="#添加谷歌测试框架" class="headerlink" title="添加谷歌测试框架"></a>添加谷歌测试框架</h3><p>使用make命令</p>
<p>一般未定义、重复定义的错误：一般均出现在链接阶段的错误。</p>
<p>而找不到头文件的错误，一般出现在预编译阶段，需要添加上头文件的编译路径。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">add</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">TEST(test, add1)&#123;</span><br><span class="line">    EXPECT_EQ(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">7</span>);</span><br><span class="line">    EXPECT_NQ(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">6</span>);</span><br><span class="line">    EXPECT_LT(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">8</span>);</span><br><span class="line">    EXPECT_LE(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">7</span>);</span><br><span class="line">    EXPECT_GT(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">6</span>);</span><br><span class="line">    EXPECT_GE(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">7</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">TEST(test, add2)&#123;</span><br><span class="line">    EXPECT_EQ(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">7</span>);</span><br><span class="line">    EXPECT_NQ(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">7</span>);</span><br><span class="line">    EXPECT_LT(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">7</span>);</span><br><span class="line">    EXPECT_LE(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">7</span>);</span><br><span class="line">    EXPECT_GT(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">7</span>);</span><br><span class="line">    EXPECT_GE(add(<span class="number">3</span>,<span class="number">4</span>),<span class="number">7</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>谷歌测试框架中Run_All：1、能够输出彩色字体；2、能够动态地获取知道有多少个测试用例</p>
<h3 id="使用printf输出彩色信息"><a href="#使用printf输出彩色信息" class="headerlink" title="使用printf输出彩色信息"></a>使用printf输出彩色信息</h3><p>使用printf输出信息之前，可以在printf添加配置参数，调整输出的字体颜色。以\033[开头，以m结尾</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">printf</span>(<span class="string">"\033[1;33;41madd(3,4) = %d\n"</span>,add(<span class="number">3</span>,<span class="number">4</span>));</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"hello world\n"</span>);</span><br></pre></td></tr></table></figure>

<p>两行的内容均会改变颜色，因为底层的信息决定了显示的颜色，<strong>而终端的程序就是****用来显示底层信息</strong>的。我们上述的修改是修改了自己配置颜色信息的部分，因此Terminal看到了设置背景色、前景色之后时，之后打印均按照该信息进行，直到后续碰到谷歌框架配置颜色信息的代码时，才按照该框架的配置来显示颜色。</p>
<p>在该配置中0为重置所有属性，故可用下列代码确保仅一行输出改变颜色,\033[0m</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">printf</span>(<span class="string">"\033[1;33;41madd(3,4) = %d\n\033[0m"</span>,add(<span class="number">3</span>,<span class="number">4</span>));</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"hello world\n"</span>);</span><br></pre></td></tr></table></figure>



<h3 id="实现调试信息log日志打印功能"><a href="#实现调试信息log日志打印功能" class="headerlink" title="实现调试信息log日志打印功能"></a>实现调试信息log日志打印功能</h3><p>在大型工程中同样的调试信息输出代码很常见，故难以判断调试信息在哪输出，因此需要能输出功能更丰富的输出调试信息。</p>
<p>预处理命令：宏定义#define :1、定义符号常量；2、定义傻瓜表达式；3、定义代码段</p>
<p>宏做的事情就是最基本的替换，发生在预处理阶段</p>
<p><em><em>C源码–预处理–》待编译      +  代码 –编译</em>–目标文件–链接–》可执行文件</em></p>
<p>g++ -E :单独执行预处理阶段</p>
<p>最终决定程序功能的并不是C源码，而是待编译源码。因此需要经过预处理阶段之后补充代码之后，才能够正确的实现功能。</p>
<p>#define:宏定义，只做基础的替换，而不做语法检查，且在编译器的视角中，宏定义一定要是一行代码。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> s(a,b) a*b</span></span><br><span class="line"></span><br><span class="line">s(<span class="keyword">int</span>, p) = &amp;n;</span><br><span class="line"></span><br><span class="line">#这句代码是正确的，因为define并不检查语法，而是只做基础替换，替换后变为： <span class="keyword">int</span> *p = &amp;n；</span><br><span class="line"></span><br><span class="line">s(<span class="number">3</span>+<span class="number">6</span>,<span class="number">4</span>);</span><br><span class="line">#预处理后成为<span class="number">3</span>+<span class="number">6</span>*<span class="number">4</span>；因此输出为<span class="number">27</span>，而不是<span class="number">4</span>*<span class="number">9</span>=<span class="number">36</span></span><br><span class="line"></span><br><span class="line">#define P(a)&#123;\</span><br><span class="line">               <span class="built_in">printf</span>(<span class="string">"%d\n"</span>, d);\&#125;</span><br><span class="line">#利用反斜杠让编译器认为上面的宏定义其实是一行代码</span><br><span class="line"></span><br><span class="line">#预定义的宏</span><br><span class="line">#_DATE_ 日期 #_TIME_ 时间 #_LINE_ 行号 #_FILE_ 文件名 </span><br><span class="line">#_func_ 函数名/非标准 #_FUNC_ 函数名/标准 </span><br><span class="line">#_PRETTY_FUNCTION_ 更详细的函数信息</span><br><span class="line">这些预定义的宏信息可用于检查版本，同样也可用于调试信息<span class="built_in">log</span>日志打印</span><br><span class="line">    </span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> log(msg) &#123;\</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">"[%s : %s : %d] %s\n"</span>, __FILE__, __func__, __LINE__, msg) ;\</span><br><span class="line">        &#125;</span><br><span class="line">    故通过<span class="built_in">log</span>能够打印出调试信息从而进行分析</span><br><span class="line">如何根据上述地修改下，实现任意参数的宏，变参函数、变参宏</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> log(frm, args...) &#123;\</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">"[%s : %s : %d] %s\n"</span>, __FILE__, __func__, __LINE__, msg) ;\</span><br><span class="line">        <span class="built_in">printf</span>(frm,##args);\</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">"\n"</span>);\</span><br><span class="line">        &#125;</span><br><span class="line"> 这样就实现了一个外在表现像是<span class="built_in">printf</span>函数的宏，其输出信息会更多许多</span><br></pre></td></tr></table></figure>

<p>#include：将后面文件内容原封不动地拷被至该位置，故待编译源码才能完整地反映功能。</p>
<p>#const：分配空间</p>
<h3 id="预处理命令-条件编译"><a href="#预处理命令-条件编译" class="headerlink" title="预处理命令-条件编译"></a>预处理命令-条件编译</h3><p>如何确保在发布版中没有日志信息，即需要用简单的方式对日志信息创建开关，必须以#endif 结尾</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> DEBUG <span class="comment">//是否定义了DEBUG宏</span></span></span><br></pre></td></tr></table></figure>

<p>预处理的条件编译只有一个作用：做代码剪裁，使用该命令决定留下哪些代码</p>
<h3 id="实现EXCEPT系列封装"><a href="#实现EXCEPT系列封装" class="headerlink" title="实现EXCEPT系列封装"></a>实现EXCEPT系列封装</h3><p>将谷歌测试框架的头文件换成自己的头文件，并编写自己头文件程序完成同样代码的编译；</p>
<p>1、实现TEST方法；2、实现未卜先知函数RUN_ALL_TEST；3、实现相等、不等、小于等判断方法，可判断不是由函数封装出来的，而是用宏进行封装；</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> _MYTEST_H</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> _MYTEST_H</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> EXPECT_EQ(a,b)&#123;\</span></span><br><span class="line">     <span class="keyword">if</span>(!((a) == (b))&#123;\</span><br><span class="line">     <span class="built_in">print</span>(<span class="string">"error\n"</span>);&#125;&#125;</span><br><span class="line"></span><br><span class="line">#define EXPECT_NE(a,b)&#123;\</span><br><span class="line">     <span class="keyword">if</span>(!((a) != (b))&#123;\</span><br><span class="line">     <span class="built_in">print</span>(<span class="string">"error\n"</span>);&#125;&#125;</span><br><span class="line"></span><br><span class="line">#define EXPECT_EQ(a,b)&#123;\</span><br><span class="line">     <span class="keyword">if</span>(!((a) &lt; (b))&#123;\</span><br><span class="line">     <span class="built_in">print</span>(<span class="string">"error\n"</span>);&#125;&#125;</span><br><span class="line">其实此处代码可以进行复用，来使代码变得简洁，用宏能做的基础替换来进行复用。</span><br><span class="line">#define EXPECT(a, comp, b)&#123;\</span><br><span class="line">      <span class="keyword">if</span>(!((a) comp (b))&#123;\</span><br><span class="line">           <span class="built_in">print</span>(<span class="string">"error\n"</span>);&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">#define EXPECT_EQ(a, b) EXPECT(a, == ,b)</span><br><span class="line">#define EXPECT_NE(a, b) EXPECT(a, != ,b)</span><br><span class="line">#define EXPECT_LT(a, b) EXPECT(a, &lt; ,b) </span><br><span class="line">#define EXPECT_LE(a, b) EXPECT(a, &lt;= ,b)</span><br><span class="line">#define EXPECT_GT(a, b) EXPECT(a, &gt; ,b)</span><br><span class="line">#define EXPECT_GE(a, b) EXPECT(a, &gt;= ,b)</span><br><span class="line"></span><br><span class="line">TEST应当是宏，在预编译后应当展开为函数的头部信息</span><br><span class="line">#define TEST(a, b) <span class="keyword">void</span> a##_##b()</span><br><span class="line"></span><br><span class="line">如何实现RUN_ALL_TEST未卜先知函数</span><br><span class="line">如何确保将测试用例函数信息写入存储区：注册函数</span><br><span class="line">#define TEST(a, b)\</span><br><span class="line"><span class="keyword">void</span> a##_##b();\</span><br><span class="line">__attribute__((constructor))\</span><br><span class="line"><span class="keyword">void</span> reg_##a##_##b()&#123;\</span><br><span class="line">    add_test_func(a##_##b, #a<span class="number">'.'</span>#b);\</span><br><span class="line">    <span class="keyword">return</span> ;\</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">struct&#123;</span><br><span class="line">    <span class="keyword">void</span>(*func)();</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *func_name;</span><br><span class="line">&#125; func_arr[<span class="number">100</span>];<span class="comment">//并非动态存储，而是只能存储100个，可用链表作用来动态开辟空间</span></span><br><span class="line">int_func_cont = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> add_test_func(<span class="keyword">void</span>(*func)(), <span class="keyword">const</span> *name)&#123;</span><br><span class="line">    func_arr[func_cnt].func =func;</span><br><span class="line">    func_Arr[func_cnt].func_name = name;</span><br><span class="line">    func_cnt += <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">return</span> ;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> RUN_ALLO_TEST&#123;\</span><br><span class="line">      <span class="comment">//遍历每一个测试用例函数（如何遍历，指向函数的指针,有一个存储区存储所有测试函数用例的地址，使用struct结构来设计存储区</span></span><br><span class="line">    <span class="comment">//依次每一个测试用例函数</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; func_cntr; i++)&#123;</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">"[  RUN  ]%S\n"</span>,func_arr[i]-&gt;name);</span><br><span class="line">        func_arr[i].func();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">                 &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#endif</span><br></pre></td></tr></table></figure>



<h2 id="5、简单算法"><a href="#5、简单算法" class="headerlink" title="5、简单算法"></a>5、简单算法</h2><p>二分查找：在一个有序数组中查找一个数据是否存在；二分函数：二分查找如何处理浮点型数据，连续函数；二分答案</p>
<p>本质：二分查找解决的问题：求解单调函数F(x)，函数与数组关系，其实和有序数组查找值一样的方法；</p>
<p>应用特点：给出X很好求，但F(X)并不好求。对于F(X) = 2X,这种正反均好求时，用不着二分查找；而对于在数组中，给出数组下标容易得出值，但给出值查找下标较为困难。</p>
<h3 id="简单版快速排序："><a href="#简单版快速排序：" class="headerlink" title="简单版快速排序："></a>简单版快速排序：</h3><p>核心：partation方法，分区方法，</p>
<p>头部指针、尾部指针：1、先尾部指针，找一个小于基准值放前面；2、再头部指针：找一个大于基准值的放后面；3、头尾指针依次进行交替，直至指针指向同一空位置；</p>
<p>写一个用于测试快速排序的程序：<em>TEST.H</em></p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TEST(func, arr, l, r)&#123;</span></span><br><span class="line">   <span class="keyword">int</span> *temp = (<span class="keyword">int</span>*)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="keyword">int</span>) * n);<span class="comment">//传入数组</span></span><br><span class="line">   <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt;n; i++) temp[i] = arr[i];<span class="comment">//将arr中函数拷贝到temp中</span></span><br><span class="line">   func(temp, <span class="number">0</span> ,n<span class="number">-1</span>);</span><br><span class="line">   <span class="keyword">if</span>(check(temp, <span class="number">0</span>, n<span class="number">-1</span>))&#123;</span><br><span class="line">       <span class="built_in">print</span>(<span class="string">"[    OK    ]%s\n"</span>, #func);</span><br><span class="line">   &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">       <span class="built_in">print</span>(<span class="string">"[    FAILED    ]%s\n"</span>, #func); </span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">check</span><span class="params">(<span class="keyword">int</span> *arr, <span class="keyword">int</span> l, <span class="keyword">int</span> r)</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = l + <span class="number">1</span>; i &lt;= r; i++)&#123;</span><br><span class="line">        <span class="keyword">if</span>(arr[i] &lt; arr[i<span class="number">-1</span>]) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> *<span class="title">getRandData</span><span class="params">(<span class="keyword">int</span> n)</span></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> *arr = (<span class="keyword">int</span>*)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="keyword">int</span>) * n);<span class="comment">//分配位置</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; n; i++) arr[i] = rand() % n;</span><br><span class="line">    <span class="keyword">return</span> arr;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> main&#123;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>





<h4 id="version1"><a href="#version1" class="headerlink" title="version1"></a>version1</h4><p>partation：选择待排序区间的第一个元素作为基准值，将小于基准值的元素放在前面，大于基准值的元素放在后面，前后指针重合时，再分别对前后两部分进行快速排序的操作。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">quick_sort_v1</span><span class="params">(<span class="keyword">int</span> *arr_, <span class="keyword">int</span> l , <span class="keyword">int</span> r)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(l &gt;= r) <span class="keyword">return</span>; <span class="comment">//递归过程第一步必然要进行边界判断</span></span><br><span class="line">    <span class="keyword">int</span> x = <span class="number">1</span>; y = r; z = arr[];<span class="comment">// Z为基准值应该在的位置</span></span><br><span class="line">    <span class="keyword">while</span>(x &lt; y)&#123;</span><br><span class="line">        <span class="keyword">while</span> (x &lt; y &amp;&amp; arr[y] &gt;= z) --y;</span><br><span class="line">        <span class="keyword">if</span>(x &lt; y) arr[x++] = arr[y];<span class="comment">//放至头指针指向的空位</span></span><br><span class="line">        <span class="keyword">while</span> (x &lt; y &amp;&amp; arr[x] &lt;= z) ++x;</span><br><span class="line">        <span class="keyword">if</span>(x &lt; y) arr[y--] = arr[x];</span><br><span class="line">    &#125;</span><br><span class="line">    arr[x] = z;<span class="comment">//最后将基准值放回其原来应该在的位置</span></span><br><span class="line">    quick_sort_v1(arr, l, x - <span class="number">1</span>);</span><br><span class="line">    quick_sort_va(arr, x + <span class="number">1</span>, r);</span><br><span class="line">    <span class="keyword">return</span> ;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>算法工程师平时考虑的是时间复杂度吗？</p>
<p>大环境下的共识：你和你身边的同事都是算法工程师，因此nlogn时间复杂度算法都能想到，关键在于在实现时代码实现的细节；</p>
<h4 id="version2：单边递归法"><a href="#version2：单边递归法" class="headerlink" title="version2：单边递归法"></a>version2：单边递归法</h4><p>当本层的快速排序做完partation操作时，会分别对左右两边进行递归操作，因此相当于一个二叉树的结构；</p>
<p>单边递归法核心思想：作为一个主管，在要被优化掉之前，做下一层的活。当前的version1函数，partation做完之后等着左右两边，可以让左半边继续递归，但右半边交给当前层程序进行执行。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">quick_sort_v2</span><span class="params">(<span class="keyword">int</span> *arr_, <span class="keyword">int</span> l , <span class="keyword">int</span> r)</span></span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(l &lt; r)&#123; </span><br><span class="line">        <span class="keyword">int</span> x = l ; y = r; z = arr[l];<span class="comment">// Z为基准值应该在的位置</span></span><br><span class="line">        <span class="keyword">while</span>(x &lt; y)&#123;</span><br><span class="line">            <span class="keyword">while</span> (x &lt; y &amp;&amp; arr[y] &gt;= z) --y;</span><br><span class="line">            <span class="keyword">if</span>(x &lt; y) arr[x++] = arr[y];<span class="comment">//放至头指针指向的空位</span></span><br><span class="line">            <span class="keyword">while</span> (x &lt; y &amp;&amp; arr[x] &lt;= z) ++x;</span><br><span class="line">            <span class="keyword">if</span>(x &lt; y) arr[y--] = arr[x];</span><br><span class="line">        &#125;</span><br><span class="line">        arr[x] = z;<span class="comment">//最后将基准值放回其原来应该在的位置</span></span><br><span class="line">        quick_sort_v2(arr, l, x - <span class="number">1</span>);</span><br><span class="line">        l = x + <span class="number">1</span>;</span><br><span class="line">    &#125;<span class="comment">//使用while，做完partation操作后，左半边排序通过递归来进行，</span></span><br><span class="line">    <span class="comment">//右半边的排序通过修改本层的</span></span><br><span class="line">    <span class="keyword">return</span> ;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="version3：无监督优化"><a href="#version3：无监督优化" class="headerlink" title="version3：无监督优化"></a>version3：无监督优化</h4><p>凡是判断坐标范围超界的判断：均为监督项，无监督优化：将监督项干掉；</p>
<p>先以插入排序来举例子：</p>
<p>插入排序思想：将无序序列分成两部分，前半部分为已排序区，后半部分为未排序区，每次从未排序区的头部选择一个元素，插入至已排序区中。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">insert_sort_v1</span><span class="params">(<span class="keyword">int</span> *arr, <span class="keyword">int</span> l, <span class="keyword">int</span> r)</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = l + <span class="number">1</span>; i &lt;= r; i++)&#123;</span><br><span class="line">        <span class="keyword">int</span> j = i;</span><br><span class="line">        <span class="keyword">while</span>(j &gt; <span class="number">0</span> &amp;&amp; arr[j] &lt; arr[j - <span class="number">1</span>])&#123;</span><br><span class="line">            swap(arr[j], arr[j<span class="number">-1</span>]);</span><br><span class="line">            --j;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>无监督思想：为何需要监督项，确保指针访问不越界，那么如何才能去掉这个监督项看上述while(j &gt; 0 &amp;&amp; arr[j] &lt; arr[j - 1])，只有当前插入的元素，是当前已排序区间的最小值，才会越界；因此，先做一个预处理，将该全局范围内的最小值先放置第一位，则不可能发生这种越界操作，则不再需要插入排序中的监督项。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">insert_sort_v2</span><span class="params">(<span class="keyword">int</span> *arr, <span class="keyword">int</span> l, <span class="keyword">int</span> r)</span></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ind = l;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = l + <span class="number">1</span>; i &lt;= r; i++)&#123;</span><br><span class="line">        <span class="keyword">if</span>(arr[ind] &gt; arr[i]) ind = i;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//插入排序是一个稳定的排序，而直接交换两个位置的值会破坏其稳定性</span></span><br><span class="line">    <span class="comment">//找到全局最小值位置后，应当将其依次一个个换至最前面</span></span><br><span class="line">    <span class="keyword">while</span> (ind &gt; l)&#123;</span><br><span class="line">        swap(arr[ind], arr[ind <span class="number">-1</span>]);</span><br><span class="line">        --ind;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = l + <span class="number">1</span>; i &lt;= r; i++)&#123;</span><br><span class="line">        <span class="keyword">int</span> j = i;</span><br><span class="line">        <span class="keyword">while</span>(j &gt; <span class="number">0</span> &amp;&amp; arr[j] &lt; arr[j - <span class="number">1</span>])&#123;</span><br><span class="line">            swap(arr[j], arr[j<span class="number">-1</span>]);</span><br><span class="line">            --j;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>那么如何实现无监督的快速排序呢？快速排序中的partation过程，小于基准值放在前，大于的放在后；逻辑上讲，确定基准值后，其前后元素的数量就已经定了，由于基准值前后的位置数量是固定的，则有前一个值往后移、则必然后后面一个元素向前移。</p>
<p>故可以头尾指针同时向中间走，且两者同时进行交换，这样就能去掉快速排序过程中其所谓的监督项了。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">quick_sort_v3</span><span class="params">(<span class="keyword">int</span> *arr_, <span class="keyword">int</span> l , <span class="keyword">int</span> r)</span></span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(l &lt; r)&#123;</span><br><span class="line">        <span class="keyword">int</span> x = <span class="number">1</span>, y = r, z = arr[l];</span><br><span class="line">        <span class="keyword">do</span>&#123;</span><br><span class="line">            <span class="keyword">while</span>(arr[x] &lt; z) ++x;</span><br><span class="line">            <span class="keyword">while</span>(arr[y] &gt; z) --y;</span><br><span class="line">            <span class="keyword">if</span>(x &lt;= y)&#123;</span><br><span class="line">                swap(arr[x], arr[y]);</span><br><span class="line">                ++x, --y;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;<span class="keyword">while</span>(x &lt;= y);</span><br><span class="line">    quick_sort_v3(arr, l, y);</span><br><span class="line">    l = x ;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>无监督的算法优化思维是一种非常重要的代码优化思维。</p>
<h4 id="version4：基准值选择优化"><a href="#version4：基准值选择优化" class="headerlink" title="version4：基准值选择优化"></a>version4：基准值选择优化</h4><p>核心思维：快速排序时间复杂度，T(n) = n * h，其中h为递归二叉树的树高，而递归二叉树最多为n个节点，logn &lt; h &lt; n；则nlogn &lt; T(n) &lt; n**2</p>
<p>如何让快排的时间复杂度稳定在nlogn：控制二叉树树高，即每一次区分左右树时，尽量让左右两边平分，即基准值能平分数组。</p>
<p>方法一：三点取中法，在头指针l，尾指针r，中间元素指针m，三者指向的值之间，选取中位的那个数值作为基准值。</p>
<h2 id="6、虚函数"><a href="#6、虚函数" class="headerlink" title="6、虚函数"></a>6、虚函数</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span> &#123;</span></span><br><span class="line">    <span class="keyword">public</span> :</span><br><span class="line">       <span class="function"><span class="keyword">void</span> <span class="title">say</span><span class="params">()</span></span>&#123;</span><br><span class="line">           <span class="built_in">cout</span> &lt;&lt; <span class="string">"this is Class A"</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">       &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">B</span> :</span> <span class="keyword">public</span> A &#123;</span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">       <span class="function"><span class="keyword">void</span> <span class="title">say</span><span class="params">()</span></span>&#123;</span><br><span class="line">           <span class="built_in">cout</span> &lt;&lt; <span class="string">"this is class B"</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">       &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    B b;</span><br><span class="line">    A &amp;a = b;</span><br><span class="line">    A *c = &amp;b;</span><br><span class="line">    b.say(); <span class="comment">//会调用B类say方法</span></span><br><span class="line">    a.say();<span class="comment">//会调用A类say方法</span></span><br><span class="line">    c-&gt;say();<span class="comment">//会调用A类say方法</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//普通的成员函数方法是跟着类走的，根据其类类别，来判断调用哪种成员方法</span></span><br><span class="line"><span class="comment">//而虚函数的方法，其调用的方法是跟着对象走的</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">C</span> &#123;</span></span><br><span class="line">    <span class="keyword">public</span> :</span><br><span class="line">       <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">say</span><span class="params">()</span></span>&#123;  <span class="comment">//前面加virtual,使其变成虚函数</span></span><br><span class="line">           <span class="built_in">cout</span> &lt;&lt; <span class="string">"this is Class A"</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">       &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">D</span> :</span> <span class="keyword">public</span> C &#123;</span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">       <span class="function"><span class="keyword">void</span> <span class="title">say</span><span class="params">()</span></span>&#123; <span class="comment">//父类的方法定义为虚函数，则其继承的子类的对应方法也会变成虚函数</span></span><br><span class="line">           <span class="built_in">cout</span> &lt;&lt; <span class="string">"this is class B"</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">       &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    B b;</span><br><span class="line">    A &amp;a = b;<span class="comment">//a虽然是A类型的引用，但它绑定的是B类型的对象</span></span><br><span class="line">    A *c = &amp;b;<span class="comment">//间接引用C时，C虽然指针类型，但它指向的是B类的对象，因此也是调用B类方法</span></span><br><span class="line">    b.say(); <span class="comment">//会调用B类say方法</span></span><br><span class="line">    a.say();<span class="comment">//会调用A类say方法</span></span><br><span class="line">    c-&gt;say();<span class="comment">//会调用A类say方法</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="为什么虚函数可以跟着对象走："><a href="#为什么虚函数可以跟着对象走：" class="headerlink" title="为什么虚函数可以跟着对象走："></a>为什么虚函数可以跟着对象走：</h3><p>任何一个对象都会占据一片存储空间，当对象中存在虚函数时，其底层存储区域中的第一个位置会记录一个地址；该地址指向一张虚函数的表vtable，表中每一项都存储的是虚函数。</p>
<p>因此虚函数是跟着对象走的，不管是何种类型的对象，都会指向了虚函数表，从而调用了当前对象所绑定的虚函数方法。 </p>
<p>假设虚函数类型为T类型，则虚函数表首地址为T* 类型， 则存储T*类型数据区的数据为T**类型。因此可用C语言中指针来提取虚函数。但若用此方法来提取带参数的虚函数时，可能会导致参数混乱。</p>
<h3 id="this指针"><a href="#this指针" class="headerlink" title="this指针"></a>this指针</h3><p>原因是：在成员方法中的特殊变量：this指针,在成员方法中，看着是一个参数，实则会添加一个隐藏参数：this指针。因此两个参数：一个是this指针指向的地址，一个才是真正的传递参数。</p>
<p>this指针其实是一个变量，是成员方法的隐藏参数。</p>
<h1 id="C-基础"><a href="#C-基础" class="headerlink" title="C++基础"></a>C++基础</h1><h2 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h2><ul>
<li><strong>对象 -</strong> 对象具有状态和行为。例如：一只狗的状态 - 颜色、名称、品种，行为 - 摇动、叫唤、吃。对象是类的实例。</li>
<li><strong>类 -</strong> 类可以定义为描述对象行为/状态的模板/蓝图。</li>
<li><strong>方法 -</strong> 从基本上说，一个方法表示一种行为。一个类可以包含多个方法。可以在方法中写入逻辑、操作数据以及执行所有的动作。</li>
<li><strong>即时变量 -</strong> 每个对象都有其独特的即时变量。对象的状态是由这些即时变量的值创建的。</li>
</ul>
<h2 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h2><table>
<thead>
<tr>
<th>布尔型</th>
<th>bool</th>
</tr>
</thead>
<tbody><tr>
<td>字符型</td>
<td>char</td>
</tr>
<tr>
<td>整型</td>
<td>int</td>
</tr>
<tr>
<td>浮点型</td>
<td>float</td>
</tr>
<tr>
<td>双浮点型</td>
<td>double</td>
</tr>
<tr>
<td>无类型</td>
<td>void</td>
</tr>
<tr>
<td>宽字符型</td>
<td>wchar_t</td>
</tr>
</tbody></table>
<p> <strong>endl</strong>，这将在每一行后插入一个换行符，&lt;&lt; 运算符用于向屏幕传多个值</p>
<p>可以使用 <strong>typedef</strong> 为一个已有的类型取一个新的名字。下面是使用 typedef 定义一个新类型的语法：</p>
<p>现在，下面的声明是完全合法的，它创建了一个整型变量 distance：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">int</span> feet;</span><br><span class="line">feet distance;</span><br></pre></td></tr></table></figure>

<h3 id="枚举类型"><a href="#枚举类型" class="headerlink" title="枚举类型"></a>枚举类型</h3><p>枚举类型(enumeration)是C++中的一种派生数据类型，它是由用户定义的若干枚举常量的集合。建枚举，需要使用关键字 <strong>enum</strong>。枚举类型的一般形式为：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> 枚举名&#123; </span><br><span class="line">     标识符[=整型常数], </span><br><span class="line">     标识符[=整型常数], </span><br><span class="line">... </span><br><span class="line">    标识符[=整型常数]</span><br><span class="line">&#125; 枚举变量;</span><br><span class="line"><span class="comment">//如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始。</span></span><br><span class="line"><span class="comment">//例如，下面的代码定义了一个颜色枚举，变量 c 的类型为 color。最后，c 被赋值为 "blue"。</span></span><br><span class="line"><span class="keyword">enum</span> color &#123; red, green, blue &#125; c;</span><br><span class="line">c = blue;</span><br></pre></td></tr></table></figure>

<h2 id="变量类型"><a href="#变量类型" class="headerlink" title="变量类型"></a>变量类型</h2><p>变量其实只不过是程序可操作的存储区的名称。C++ 中每个变量都有指定的类型，类型决定了变量存储的大小和布局，该范围内的值都可以存储在内存中，运算符可应用于变量上。</p>
<table>
<thead>
<tr>
<th>bool</th>
<th align="center">存储值 true 或 false。</th>
</tr>
</thead>
<tbody><tr>
<td>char</td>
<td align="center">通常是一个字符（八位）。这是一个整数类型。</td>
</tr>
<tr>
<td>int</td>
<td align="center">对机器而言，整数的最自然的大小。</td>
</tr>
<tr>
<td>float</td>
<td align="center">单精度浮点值。单精度是这样的格式，1位符号，8位指数，23位小数。<img src="https://www.runoob.com/wp-content/uploads/2014/09/v2-749cc641eb4d5dafd085e8c23f8826aa_hd.png" alt="img"></td>
</tr>
<tr>
<td>double</td>
<td align="center">双精度浮点值。双精度是1位符号，11位指数，52位小数。<img src="https://www.runoob.com/wp-content/uploads/2014/09/v2-48240f0e1e0dd33ec89100cbe2d30707_hd.png" alt="img"></td>
</tr>
<tr>
<td>void</td>
<td align="center">表示类型的缺失。</td>
</tr>
<tr>
<td>wchar_t</td>
<td align="center">宽字符类型。</td>
</tr>
</tbody></table>
<p>变量定义就是告诉编译器在何处创建变量的存储，以及如何创建变量的存储。变量定义指定一个数据类型，并包含了该类型的一个或多个变量的列表，</p>
<p>变量声明向编译器保证变量以给定的类型和名称存在，这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义，在程序连接时编译器需要实际的变量声明。</p>
<p>当您使用多个文件且只在其中一个文件中定义变量时（定义变量的文件在程序连接时是可用的），变量声明就显得非常有用。您可以使用 <strong>extern</strong> 关键字在任何地方声明一个变量。虽然您可以在 C++ 程序中多次声明一个变量，但变量只能在某个文件、函数或代码块中被定义一次。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 变量声明</span></span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">int</span> a, b;</span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">int</span> c;</span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">float</span> f;</span><br><span class="line">  </span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span> <span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="comment">// 变量定义</span></span><br><span class="line">  <span class="keyword">int</span> a, b;</span><br><span class="line">  <span class="keyword">int</span> c;</span><br><span class="line">  <span class="keyword">float</span> f;</span><br><span class="line"> </span><br><span class="line">  <span class="comment">// 实际初始化</span></span><br><span class="line">  a = <span class="number">10</span>;</span><br><span class="line">  b = <span class="number">20</span>;</span><br><span class="line">  c = a + b;</span><br><span class="line"> </span><br><span class="line">  <span class="built_in">cout</span> &lt;&lt; c &lt;&lt; <span class="built_in">endl</span> ;</span><br><span class="line"> </span><br><span class="line">  f = <span class="number">70.0</span>/<span class="number">3.0</span>;</span><br><span class="line">  <span class="built_in">cout</span> &lt;&lt; f &lt;&lt; <span class="built_in">endl</span> ;</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>同样的，在函数声明时，提供一个函数名，而函数的实际定义则可以在任何地方进行。例如</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 函数声明</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">()</span></span>;</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 函数调用</span></span><br><span class="line">    <span class="keyword">int</span> i = func();</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 函数定义</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>C++ 中有两种类型的表达式：</p>
<ul>
<li><strong>左值（lvalue）：</strong>指向内存位置、的表达式被称为左值（lvalue）表达式。左值可以出现在赋值号的左边或右边。</li>
<li><strong>右值（rvalue）：</strong>术语右值（rvalue）指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式，也就是说，右值可以出现在赋值号的右边，但不能出现在赋值号的左边。</li>
</ul>
<p>变量是左值，因此可以出现在赋值号的左边。数值型的字面值是右值，因此不能被赋值，不能出现在赋值号的左边。下面是一个有效的语句：</p>
<h2 id="变量作用域"><a href="#变量作用域" class="headerlink" title="变量作用域"></a>变量作用域</h2><p>作用域是程序的一个区域，一般来说有三个地方可以定义变量：</p>
<ul>
<li>在函数或一个代码块内部声明的变量，称为局部变量。</li>
<li>在函数参数的定义中声明的变量，称为形式参数。</li>
<li>在所有函数外部声明的变量，称为全局变量。</li>
</ul>
<p>在函数或一个代码块内部声明的变量，称为局部变量。它们只能被函数内部或者代码块内部的语句使用。</p>
<p>在所有函数外部定义的变量（通常是在程序的头部），称为全局变量。全局变量的值在程序的整个生命周期内都是有效的。</p>
<p>在程序中，局部变量和全局变量的名称可以相同，但是在函数内，局部变量的值会覆盖全局变量的值。</p>
<h2 id="字面量"><a href="#字面量" class="headerlink" title="字面量"></a>字面量</h2><p>常量是固定值，在程序执行期间不会改变。这些固定的值，又叫做<strong>字面量</strong>。</p>
<p>常量可以是任何的基本数据类型，可分为整型数字、浮点数字、字符、字符串和布尔值。</p>
<p>在 C++ 中，有两种简单的定义常量的方式：</p>
<ul>
<li>使用 <strong>#define</strong> 预处理器。</li>
<li>使用 <strong>const</strong> 关键字。</li>
</ul>
<h2 id="修饰符类型"><a href="#修饰符类型" class="headerlink" title="修饰符类型"></a>修饰符类型</h2><p>C++ 允许在 <strong>char、int 和 double</strong> 数据类型前放置修饰符。修饰符用于改变基本类型的含义，所以它更能满足各种情境的需求。</p>
<p>修饰符 <strong>signed、unsigned、long 和 short</strong> 可应用于整型，<strong>signed</strong> 和 <strong>unsigned</strong> 可应用于字符型，<strong>long</strong> 可应用于双精度型。</p>
<p>修饰符 <strong>signed</strong> 和 <strong>unsigned</strong> 也可以作为 <strong>long</strong> 或 <strong>short</strong> 修饰符的前缀。例如：<strong>unsigned long int</strong>。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//为了理解 C++ 解释有符号整数和无符号整数修饰符之间的差别，我们来运行一下下面这个短程序：</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"> </span><br><span class="line"><span class="comment">/* </span></span><br><span class="line"><span class="comment"> * 这个程序演示了有符号整数和无符号整数之间的差别</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   short <span class="keyword">int</span> i;           <span class="comment">// 有符号短整数</span></span><br><span class="line">   short <span class="keyword">unsigned</span> <span class="keyword">int</span> j;  <span class="comment">// 无符号短整数</span></span><br><span class="line"> </span><br><span class="line">   j = <span class="number">50000</span>;</span><br><span class="line"> </span><br><span class="line">   i = j;</span><br><span class="line">   <span class="built_in">cout</span> &lt;&lt; i &lt;&lt; <span class="string">" "</span> &lt;&lt; j;</span><br><span class="line"> </span><br><span class="line">   <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//输出-15536 50000</span></span><br><span class="line"><span class="comment">//上述结果中，无符号短整数 50,000 的位模式被解释为有符号短整数 -15,536</span></span><br></pre></td></tr></table></figure>

<h2 id="存储类"><a href="#存储类" class="headerlink" title="存储类"></a>存储类</h2><p>存储类定义 C++ 程序中变量/函数的范围（可见性）和生命周期。这些说明符放置在它们所修饰的类型之前。下面列出 C++ 程序中可用的存储类：</p>
<ul>
<li>auto</li>
<li>register</li>
<li>static</li>
<li>extern</li>
<li>mutable</li>
<li>thread_local (C++11)</li>
</ul>
<h3 id="auto-存储类"><a href="#auto-存储类" class="headerlink" title="auto 存储类"></a>auto 存储类</h3><p>自 C++ 11 以来，<strong>auto</strong> 关键字用于两种情况：声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。</p>
<h3 id="register-存储类"><a href="#register-存储类" class="headerlink" title="register 存储类"></a>register 存储类</h3><p><strong>register</strong> 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小（通常是一个词），且不能对它应用一元的 ‘&amp;’ 运算符（因为它没有内存位置）</p>
<p>寄存器只用于需要快速访问的变量，比如计数器。</p>
<h3 id="static-存储类"><a href="#static-存储类" class="headerlink" title="static 存储类"></a>static 存储类</h3><p><strong>static</strong> 存储类指示编译器在程序的生命周期内保持局部变量的存在，而不需要在每次它进入和离开作用域时进行创建和销毁。因此，使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。</p>
<p>static 修饰符也可以应用于全局变量。当 static 修饰全局变量时，会使变量的作用域限制在声明它的文件内。在 C++ 中，当 static 用在类数据成员上时，会导致仅有一个该成员的副本被类的所有对象共享。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"> </span><br><span class="line"><span class="comment">// 函数声明 </span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">static</span> <span class="keyword">int</span> count = <span class="number">10</span>; <span class="comment">/* 全局变量 */</span></span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(count--)</span><br><span class="line">    &#123;</span><br><span class="line">       func();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 函数定义</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">( <span class="keyword">void</span> )</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">int</span> i = <span class="number">5</span>; <span class="comment">// 局部静态变量</span></span><br><span class="line">    i++;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"变量 i 为 "</span> &lt;&lt; i ;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">" , 变量 count 为 "</span> &lt;&lt; count &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="extern-存储类"><a href="#extern-存储类" class="headerlink" title="extern 存储类"></a>extern 存储类</h3><p><strong>extern</strong> 存储类用于提供一个全局变量的引用，全局变量对所有的程序文件都是可见的。当您使用 ‘extern’ 时，对于无法初始化的变量，会把变量名指向一个之前定义过的存储位置。</p>
<p>当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时，可以在其他文件中使用 <em>extern</em> 来得到已定义的变量或函数的引用。可以这么理解，<em>extern</em> 是用来在另一个文件中声明一个全局变量或函数。</p>
<p>extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候，如下所示：</p>
<p>第一个文件：main.cpp</p>
<p>在这里，第二个文件中的 <em>extern</em> 关键字用于声明已经在第一个文件 main.cpp 中定义的 count。现在 ，编译这两个文件，如下所示</p>
<h2 id="mutable-存储类"><a href="#mutable-存储类" class="headerlink" title="mutable 存储类"></a>mutable 存储类</h2><p><strong>mutable</strong> 说明符仅适用于类的对象，这将在本教程的最后进行讲解。它允许对象的成员替代常量。也就是说，mutable 成员可以通过 const 成员函数修改。</p>
<h2 id="thread-local-存储类"><a href="#thread-local-存储类" class="headerlink" title="thread_local 存储类"></a>thread_local 存储类</h2><p>使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建，并在销毁线程时销毁。 每个线程都有其自己的变量副本。</p>
<p>thread_local 说明符可以与 static 或 extern 合并。可以将 thread_local 仅应用于数据声明和定义，thread_local 不能用于函数声明或定义。以下演示了可以被声明为 thread_local 的变量：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">thread_local</span> <span class="keyword">int</span> x;  <span class="comment">// 命名空间下的全局变量</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">X</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">thread_local</span> <span class="built_in">std</span>::<span class="built_in">string</span> s; <span class="comment">// 类的static成员变量</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">thread_local</span> <span class="built_in">std</span>::<span class="built_in">string</span> X::s;  <span class="comment">// X::s 是需要定义的</span></span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">foo</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">thread_local</span> <span class="built_in">std</span>::<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; v;  <span class="comment">// 本地变量</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="运算符"><a href="#运算符" class="headerlink" title="运算符"></a>运算符</h2><ul>
<li><p>算术运算符 + - * / % ++ –</p>
</li>
<li><p>关系运算符 == != &gt; &lt; &gt;= &lt;=</p>
</li>
<li><p>逻辑运算符 &amp;&amp; || !</p>
</li>
<li><p>位运算符 </p>
<table>
<thead>
<tr>
<th><strong>&amp;</strong></th>
<th><strong>如果同时存在于两个操作数中，二进制 AND 运算符复制一位到结果中。</strong></th>
<th><strong>(A &amp; B) 将得到 12，即为 0000 1100</strong></th>
</tr>
</thead>
<tbody><tr>
<td>|</td>
<td>如果存在于任一操作数中，二进制 OR 运算符复制一位到结果中。</td>
<td>(A | B) 将得到 61，即为 0011 1101</td>
</tr>
<tr>
<td>^</td>
<td>如果存在于其中一个操作数中但不同时存在于两个操作数中，二进制异或运算符复制一位到结果中。</td>
<td>(A ^ B) 将得到 49，即为 0011 0001</td>
</tr>
<tr>
<td>~</td>
<td>二进制补码运算符是一元运算符，具有”翻转”位效果，即0变成1，1变成0。</td>
<td>(~A ) 将得到 -61，即为 1100 0011，一个有符号二进制数的补码形式。</td>
</tr>
<tr>
<td>&lt;&lt;</td>
<td>二进制左移运算符。左操作数的值向左移动右操作数指定的位数。</td>
<td>A &lt;&lt; 2 将得到 240，即为 1111 0000</td>
</tr>
<tr>
<td>&gt;&gt;</td>
<td>二进制右移运算符。左操作数的值向右移动右操作数指定的位数。</td>
<td>A &gt;&gt; 2 将得到 15，即为 0000 1111</td>
</tr>
</tbody></table>
</li>
<li><p>赋值运算符</p>
<table>
<thead>
<tr>
<th>=</th>
<th>简单的赋值运算符，把右边操作数的值赋给左边操作数</th>
<th>C = A + B 将把 A + B 的值赋给 C</th>
</tr>
</thead>
<tbody><tr>
<td>+=</td>
<td>加且赋值运算符，把右边操作数加上左边操作数的结果赋值给左边操作数</td>
<td>C += A 相当于 C = C + A</td>
</tr>
<tr>
<td>-=</td>
<td>减且赋值运算符，把左边操作数减去右边操作数的结果赋值给左边操作数</td>
<td>C -= A 相当于 C = C - A</td>
</tr>
<tr>
<td>*=</td>
<td>乘且赋值运算符，把右边操作数乘以左边操作数的结果赋值给左边操作数</td>
<td>C *= A 相当于 C = C * A</td>
</tr>
<tr>
<td>/=</td>
<td>除且赋值运算符，把左边操作数除以右边操作数的结果赋值给左边操作数</td>
<td>C /= A 相当于 C = C / A</td>
</tr>
<tr>
<td>%=</td>
<td>求模且赋值运算符，求两个操作数的模赋值给左边操作数</td>
<td>C %= A 相当于 C = C % A</td>
</tr>
<tr>
<td>&lt;&lt;=</td>
<td>左移且赋值运算符</td>
<td>C &lt;&lt;= 2 等同于 C = C &lt;&lt; 2</td>
</tr>
<tr>
<td>&gt;&gt;=</td>
<td>右移且赋值运算符</td>
<td>C &gt;&gt;= 2 等同于 C = C &gt;&gt; 2</td>
</tr>
<tr>
<td>&amp;=</td>
<td>按位与且赋值运算符</td>
<td>C &amp;= 2 等同于 C = C &amp; 2</td>
</tr>
<tr>
<td>^=</td>
<td>按位异或且赋值运算符</td>
<td>C ^= 2 等同于 C = C ^ 2</td>
</tr>
<tr>
<td>|=</td>
<td>按位或且赋值运算符</td>
<td>C |= 2 等同于 C = C | 2</td>
</tr>
</tbody></table>
</li>
<li><p>杂项运算符</p>
</li>
</ul>
<table>
<thead>
<tr>
<th>sizeof</th>
<th><a href="https://www.runoob.com/cplusplus/cpp-sizeof-operator.html" target="_blank" rel="noopener">sizeof 运算符</a>返回变量的大小。例如，sizeof(a) 将返回 4，其中 a 是整数。</th>
</tr>
</thead>
<tbody><tr>
<td>Condition ? X : Y</td>
<td><a href="https://www.runoob.com/cplusplus/cpp-conditional-operator.html" target="_blank" rel="noopener">条件运算符</a>。如果 Condition 为真 ? 则值为 X : 否则值为 Y。</td>
</tr>
<tr>
<td>,</td>
<td><a href="https://www.runoob.com/cplusplus/cpp-comma-operator.html" target="_blank" rel="noopener">逗号运算符</a>会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。</td>
</tr>
<tr>
<td>.（点）和 -&gt;（箭头）</td>
<td><a href="https://www.runoob.com/cplusplus/cpp-member-operators.html" target="_blank" rel="noopener">成员运算符</a>用于引用类、结构和共用体的成员。</td>
</tr>
<tr>
<td>Cast</td>
<td><a href="https://www.runoob.com/cplusplus/cpp-casting-operators.html" target="_blank" rel="noopener">强制转换运算符</a>把一种数据类型转换为另一种数据类型。例如，int(2.2000) 将返回 2。</td>
</tr>
<tr>
<td>&amp;</td>
<td><a href="https://www.runoob.com/cplusplus/cpp-pointer-operators.html" target="_blank" rel="noopener">指针运算符 &amp;</a> 返回变量的地址。例如 &a; 将给出变量的实际地址。</td>
</tr>
<tr>
<td>*</td>
<td><a href="https://www.runoob.com/cplusplus/cpp-pointer-operators.html" target="_blank" rel="noopener">指针运算符 *</a> 指向一个变量。例如，*var; 将指向变量 var。</td>
</tr>
</tbody></table>
<h2 id="循环"><a href="#循环" class="headerlink" title="循环"></a>循环</h2><p>循环控制语句更改执行的正常序列。当执行离开一个范围时，所有在该范围中创建的自动对象都会被销毁。</p>
<table>
<thead>
<tr>
<th align="left">控制语句</th>
<th align="left">描述</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><a href="https://www.runoob.com/cplusplus/cpp-break-statement.html" target="_blank" rel="noopener">break 语句</a></td>
<td align="left">终止 <strong>loop</strong> 或 <strong>switch</strong> 语句，程序流将继续执行紧接着 loop 或 switch 的下一条语句。</td>
</tr>
<tr>
<td align="left"><a href="https://www.runoob.com/cplusplus/cpp-continue-statement.html" target="_blank" rel="noopener">continue 语句</a></td>
<td align="left">引起循环跳过主体的剩余部分，立即重新开始测试条件。</td>
</tr>
<tr>
<td align="left"><a href="https://www.runoob.com/cplusplus/cpp-goto-statement.html" target="_blank" rel="noopener">goto 语句</a></td>
<td align="left">将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。</td>
</tr>
</tbody></table>
<h2 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h2><h3 id="调用函数"><a href="#调用函数" class="headerlink" title="调用函数"></a>调用函数</h3><p>创建 C++ 函数时，会定义函数做什么，然后通过调用函数来完成已定义的任务。</p>
<p>当程序调用函数时，程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务，当函数的返回语句被执行时，或到达函数的结束括号时，会把程序控制权交还给主程序。</p>
<p>调用函数时，传递所需参数，如果函数返回一个值，则可以存储返回值。例如：</p>
<h3 id="函数参数"><a href="#函数参数" class="headerlink" title="函数参数"></a>函数参数</h3><p>如果函数要使用参数，则必须声明接受参数值的变量。这些变量称为函数的<strong>形式参数</strong>。</p>
<p>形式参数就像函数内的其他局部变量，在进入函数时被创建，退出函数时被销毁。</p>
<p>当调用函数时，有三种向函数传递参数的方式：</p>
<table>
<thead>
<tr>
<th align="left">调用类型</th>
<th align="left">描述</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><a href="https://www.runoob.com/cplusplus/cpp-function-call-by-value.html" target="_blank" rel="noopener">传值调用</a></td>
<td align="left">该方法把参数的实际值赋值给函数的形式参数。在这种情况下，修改函数内的形式参数对实际参数没有影响。</td>
</tr>
<tr>
<td align="left"><a href="https://www.runoob.com/cplusplus/cpp-function-call-by-pointer.html" target="_blank" rel="noopener">指针调用</a></td>
<td align="left">该方法把参数的地址赋值给形式参数。在函数内，该地址用于访问调用中要用到的实际参数。这意味着，修改形式参数会影响实际参数。</td>
</tr>
<tr>
<td align="left"><a href="https://www.runoob.com/cplusplus/cpp-function-call-by-reference.html" target="_blank" rel="noopener">引用调用</a></td>
<td align="left">该方法把参数的引用赋值给形式参数。在函数内，该引用用于访问调用中要用到的实际参数。这意味着，修改形式参数会影响实际参数。</td>
</tr>
</tbody></table>
<p>默认情况下，C++ 使用传值调用来传递参数。一般来说，这意味着函数内的代码不能改变用于调用函数的参数。之前提到的实例，调用 max() 函数时，使用了相同的方法。</p>
<p>向函数传递参数的<strong>引用调用</strong>方法，把引用的地址复制给形式参数。在函数内，该引用用于访问调用中要用到的实际参数。这意味着，修改形式参数会影响实际参数。</p>
<p>按引用传递值，参数引用被传递给函数，就像传递其他值给函数一样。因此相应地，在下面的函数 <strong>swap()</strong> 中，您需要声明函数参数为引用类型，该函数用于交换参数所指向的两个整数变量的值。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 函数定义</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span> &amp;x, <span class="keyword">int</span> &amp;y)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   <span class="keyword">int</span> temp;</span><br><span class="line">   temp = x; <span class="comment">/* 保存地址 x 的值 */</span></span><br><span class="line">   x = y;    <span class="comment">/* 把 y 赋值给 x */</span></span><br><span class="line">   y = temp; <span class="comment">/* 把 x 赋值给 y  */</span></span><br><span class="line">  </span><br><span class="line">   <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递，如果有大块数据作为参数传递的时候，采用的方案往往是指针，因为 这样可以避免将整块数据全部压栈，可以提高程序的效率。但是现在（C++中）又增加了一种同样有效率的选择（在某些特殊情况下又是必须的选择），就是引 用。</p>
<p>如果既要利用引用提高程序的效率，又要保护传递给函数的数据不在函数中被改变，就应使用常引用。常引用声明方式：const 类型标识符 &amp;引用名=目标变量名；用这种方式声明的引用，不能通过引用对目标变量的值进行修改,从而使引用的目标成为const，达到了引用的安全性。</p>
<p>要以引用返回函数值，则函数定义时要按以下格式：</p>
<p>类型标识符 &amp;函数名（形参列表及类型说明）<br>{函数体}</p>
<p>说明：（1）以引用返回函数值，定义函数时需要在函数名前加&amp;</p>
<p>（2）用引用返回一个函数值的最大好处是，在内存中不产生被返回值的副本。</p>
<h2 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h2><p>多维数组：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//多维数组声明的一般形式如下：</span></span><br><span class="line">type name[size1][size2]...[sizeN];</span><br><span class="line"><span class="comment">//例如，下面的声明创建了一个三维 5 . 10 . 4 整型数组：</span></span><br><span class="line"><span class="keyword">int</span> threedim[<span class="number">5</span>][<span class="number">10</span>][<span class="number">4</span>];</span><br><span class="line"><span class="comment">//多维数组最简单的形式是二维数组。一个二维数组，在本质上，是一个一维数组的列表。声明一个 x 行 y 列的二维整型数组，形式如下：</span></span><br><span class="line"><span class="keyword">int</span> a[<span class="number">3</span>][<span class="number">4</span>] = &#123;  </span><br><span class="line"> &#123;<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125; ,   <span class="comment">/*  初始化索引号为 0 的行 */</span></span><br><span class="line"> &#123;<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>&#125; ,   <span class="comment">/*  初始化索引号为 1 的行 */</span></span><br><span class="line"> &#123;<span class="number">8</span>, <span class="number">9</span>, <span class="number">10</span>, <span class="number">11</span>&#125;   <span class="comment">/*  初始化索引号为 2 的行 */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>指向数组的指针：runoobAarray 是一个指向 &amp;runoobAarray[0] 的指针，即数组 runoobAarray 的第一个元素的地址。一旦您把第一个元素的地址存储在 p 中，您就可以使用 <em>p、</em>(p+1)、*(p+2) 等来访问数组元素。</p>
<p>C++ 中您可以通过指定不带索引的数组名来传递一个指向数组的指针。C++ 传数组给一个函数，数组类型自动转换为指针类型，因而传的实际是地址。</p>
<p>如果您想要在函数中传递一个一维数组作为参数，您必须以下面三种方式来声明函数形式参数，这三种声明方式的结果是一样的，因为每种方式都会告诉编译器将要接收一个整型指针。同样地，您也可以传递一个多维数组作为形式参数。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">myFunction</span><span class="params">(<span class="keyword">int</span> *param)</span><span class="comment">//param[10]//param[]</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>C++ 不允许返回一个完整的数组作为函数的参数。但是，您可以通过指定不带索引的数组名来返回一个指向数组的指针。</p>
<p>如果您想要从函数返回一个一维数组，您必须声明一个返回指针的函数，如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> * <span class="title">myFunction</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">.</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//另外，C++ 不支持在函数外返回局部变量的地址，除非定义局部变量为 static 变量。</span></span><br><span class="line"><span class="comment">//现在，让我们来看下面的函数，它会生成 10 个随机数，并使用数组来返回它们，具体如下：</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;cstdlib&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;ctime&gt;</span></span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 要生成和返回随机数的函数</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> * <span class="title">getRandom</span><span class="params">( )</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">int</span>  r[<span class="number">10</span>];</span><br><span class="line"> </span><br><span class="line">  <span class="comment">// 设置种子</span></span><br><span class="line">  srand( (<span class="keyword">unsigned</span>)time( <span class="literal">NULL</span> ) );</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; ++i)</span><br><span class="line">  &#123;</span><br><span class="line">    r[i] = rand();</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; r[i] &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">return</span> r;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 要调用上面定义函数的主函数</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span> <span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   <span class="comment">// 一个指向整数的指针</span></span><br><span class="line">   <span class="keyword">int</span> *p;</span><br><span class="line"> </span><br><span class="line">   p = getRandom();</span><br><span class="line">   <span class="keyword">for</span> ( <span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++ )</span><br><span class="line">   &#123;</span><br><span class="line">       <span class="built_in">cout</span> &lt;&lt; <span class="string">"*(p + "</span> &lt;&lt; i &lt;&lt; <span class="string">") : "</span>;</span><br><span class="line">       <span class="built_in">cout</span> &lt;&lt; *(p + i) &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><p>引用很容易与指针混淆，它们之间有三个主要的不同：</p>
<ul>
<li>不存在空引用。引用必须连接到一块合法的内存。</li>
<li>一旦引用被初始化为一个对象，就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。</li>
<li>引用必须在创建时被初始化。指针可以在任何时间被初始化。</li>
</ul>
<p>试想变量名称是变量附属在内存位置中的标签，您可以把引用当成是变量附属在内存位置中的第二个标签。因此，您可以通过原始变量名称或引用来访问变量的内容。例如：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span>&amp;  r = i;</span><br><span class="line"><span class="keyword">double</span>&amp; s = d;</span><br></pre></td></tr></table></figure>

<p>在这些声明中，&amp; 读作<strong>引用</strong>。因此，第一个声明可以读作 “r 是一个初始化为 i 的整型引用”，第二个声明可以读作 “s 是一个初始化为 d 的 double 型引用”。下面的实例使用了 int 和 double 引用：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span> <span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   <span class="comment">// 声明简单的变量</span></span><br><span class="line">   <span class="keyword">int</span>    i;</span><br><span class="line">   <span class="keyword">double</span> d;</span><br><span class="line"> </span><br><span class="line">   <span class="comment">// 声明引用变量</span></span><br><span class="line">   <span class="keyword">int</span>&amp;    r = i;</span><br><span class="line">   <span class="keyword">double</span>&amp; s = d;</span><br><span class="line">   </span><br><span class="line">   i = <span class="number">5</span>;</span><br><span class="line">   <span class="built_in">cout</span> &lt;&lt; <span class="string">"Value of i : "</span> &lt;&lt; i &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">   <span class="built_in">cout</span> &lt;&lt; <span class="string">"Value of i reference : "</span> &lt;&lt; r  &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line"> </span><br><span class="line">   d = <span class="number">11.7</span>;</span><br><span class="line">   <span class="built_in">cout</span> &lt;&lt; <span class="string">"Value of d : "</span> &lt;&lt; d &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">   <span class="built_in">cout</span> &lt;&lt; <span class="string">"Value of d reference : "</span> &lt;&lt; s  &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">   </span><br><span class="line">   <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果如下：</span></span><br><span class="line"><span class="comment">Value of i : 5</span></span><br><span class="line"><span class="comment">Value of i reference : 5</span></span><br><span class="line"><span class="comment">Value of d : 11.7</span></span><br><span class="line"><span class="comment">Value of d reference : 11.7</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>


      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2020/08/15/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E9%A6%96%E7%AF%87%E5%8D%9A%E6%96%87/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2020/08/15/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E9%A6%96%E7%AF%87%E5%8D%9A%E6%96%87/" class="post-title-link" itemprop="url">基于HEXO的博客搭建</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2020-08-15 19:11:49" itemprop="dateCreated datePublished" datetime="2020-08-15T19:11:49+08:00">2020-08-15</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:36:21" itemprop="dateModified" datetime="2021-11-03T16:36:21+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2/" itemprop="url" rel="index"><span itemprop="name">前端页面</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><p>1、Git下载并利用SSH密钥关联上GitHub账号</p>
<p>2、JS.node下载</p>
<p>3、npm、cnpm、hexo下载</p>
<h2 id="创建博客并利用GitHub-Pages上传网络端"><a href="#创建博客并利用GitHub-Pages上传网络端" class="headerlink" title="创建博客并利用GitHub Pages上传网络端"></a>创建博客并利用GitHub Pages上传网络端</h2><p>1、创建空文件夹，并使用hexo init 生成博客</p>
<p>2、利用GitHub生成新的仓库作为博客资源，在原blog文件夹下安装GIT，并部署至该仓库</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  


  
  <nav class="pagination">
    <a class="extend prev" rel="prev" href="/page/9/"><i class="fa fa-angle-left" aria-label="上一页"></i></a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/9/">9</a><span class="page-number current">10</span>
  </nav>



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Li Yudong"
      src="/images/author.jpg">
  <p class="site-author-name" itemprop="name">Li Yudong</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">95</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">14</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">18</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/tiarmor1" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;tiarmor1" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:1157019137@qq.com" title="E-Mail → mailto:1157019137@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>
  <div class="cc-license motion-element" itemprop="license">
    <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" class="cc-opacity" rel="noopener" target="_blank"><img src="/images/cc-by-nc-sa.svg" alt="Creative Commons"></a>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 2020 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Li Yudong</span>
</div>

        








      </div>
    </footer>
  </div>

  
  
  <script color='0,0,0' opacity='0.5' zIndex='-1' count='150' src="/lib1/canvas-nest/canvas-nest.min.js"></script>
  <script src="/lib1/anime.min.js"></script>
  <script src="/lib1/velocity/velocity.min.js"></script>
  <script src="/lib1/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  

  
   <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> 
   <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script> 
   <script type="text/javascript" src="/js/fireworks.js"></script>
  
</body>
</html>

